Merge branch 'feature/mohammad_refactor_filter' into 'develop'

complete toast and notification for keeping data in local storage for...

See merge request witel-front-end/rms!7
This commit is contained in:
AmirHossein Mahmoodi
2024-07-08 07:24:49 +00:00
37 changed files with 1065 additions and 812 deletions

View File

@@ -22,12 +22,15 @@
"formik": "^2.4.5",
"jalali-moment": "^3.3.11",
"material-react-table": "^2.11.2",
"next": "14.1.0",
"next": "^14.2.3",
"react": "^18",
"react-dom": "^18",
"react-toastify": "^10.0.5",
"sass": "^1.75.0",
"stylis": "^4.3.1",
"stylis-plugin-rtl": "^2.1.1"
"stylis-plugin-rtl": "^2.1.1",
"swr": "^2.2.5",
"yup": "^1.4.0"
},
"devDependencies": {
"eslint": "^8",

View File

@@ -1 +1,7 @@
@import "fontiran";
@import "fontiran";
.filter-toast {
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
background-color: #d0dfe8;
border: 1px dashed #095b8c;
}

View File

@@ -1,6 +1,10 @@
import 'react-toastify/dist/ReactToastify.css';
const Layout = ({children}) => {
return (
<>{children}</>
<>
{children}
</>
)
}

View File

@@ -1,6 +1,7 @@
import {Box, Stack} from "@mui/material";
import HeaderWithLogo from "@/components/layouts/dashboard/headerWithLogo";
import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar";
import 'react-toastify/dist/ReactToastify.css';
const Template = ({children}) => {
return (<Stack>

View File

@@ -2,7 +2,9 @@ import cacheProviderRtl from "@/core/utils/cacheRtl";
import {CssBaseline, ThemeProvider} from "@mui/material";
import theme from "@/core/utils/theme";
import {AppRouterCacheProvider} from "@mui/material-nextjs/v14-appRouter";
import "^/global.scss"
import 'react-toastify/dist/ReactToastify.css';
import "^/global.scss";
import {ToastContainer} from "react-toastify";
const Template = ({children}) => {
return (
@@ -10,6 +12,7 @@ const Template = ({children}) => {
<ThemeProvider theme={theme}>
<CssBaseline/>
{children}
<ToastContainer rtl containerId="filtering" closeButton={false}/>
</ThemeProvider>
</AppRouterCacheProvider>
)

View File

@@ -2,7 +2,9 @@ import {Button} from "@mui/material";
const Toolbar = ({columns}) => {
return (
<Button>add something</Button>
<>
<Button>add something</Button>
</>
)
}
export default Toolbar

View File

@@ -17,7 +17,6 @@ function HomeComponent() {
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
inputType: "textField",
enableClickToCopy: true,
columnFilterModeOptions: [
"equals",
@@ -32,7 +31,6 @@ function HomeComponent() {
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
inputType: "textField",
columnFilterModeOptions: [
"equals",
"notEquals",
@@ -48,7 +46,6 @@ function HomeComponent() {
enableColumnFilter: true,
datatype: "text",
filterFn: "notEquals",
inputType: "textField",
columnFilterModeOptions: [
"equals",
"notEquals",
@@ -62,7 +59,6 @@ function HomeComponent() {
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
inputType: "textField",
columnFilterModeOptions: [
"equals",
"notEquals",
@@ -71,7 +67,6 @@ function HomeComponent() {
"greaterThan",
"fuzzy"
],
},
{
accessorKey: 'state',
@@ -80,7 +75,6 @@ function HomeComponent() {
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
inputType: "textField",
columnFilterModeOptions: [
"equals",
"notEquals",
@@ -92,9 +86,20 @@ function HomeComponent() {
header: 'محدوده',
id: "price",
enableColumnFilter: true,
datatype: "nemeric",
datatype: "numeric",
filterFn: "equals",
columnFilterModeOptions: [
"equals",
"notEquals",
],
},
{
accessorKey: 'created_at',
header: 'تاریخ ساخت',
id: "created_at",
enableColumnFilter: true,
datatype: "date",
filterFn: "between",
inputType: "betweenNum",
},
{
accessorKey: 'updated_at',
@@ -102,26 +107,33 @@ function HomeComponent() {
id: "updated_at",
enableColumnFilter: true,
datatype: "date",
filterFn: "between",
inputType: "betweenDate",
filterFn: "equals",
},
{
accessorKey: 'company',
header: 'نام شرکت',
id: "company",
enableColumnFilter: true,
datatype: "text",
datatype: "numeric",
filterFn: "equals",
inputType: "select2",
columnSelectOption: [
{value: 1, label: "وایتل"},
{value: 2, label: "اسنپ"},
{value: 3, label: "تپسی"}
],
},
{
accessorKey: 'area',
header: 'منطقه',
id: "area",
enableColumnFilter: true,
datatype: "text",
datatype: "array",
filterFn: "equals",
inputType: "select",
columnSelectOption: [
{value: 1, label: "وایتل"},
{value: 2, label: "اسنپ"},
{value: 3, label: "تپسی"}
],
},
],
[],
@@ -129,7 +141,7 @@ function HomeComponent() {
return (
<>
<DataTable columns={columns} userId={0} pageName={'testPage'} tableName={'testTable'}/>
<DataTable columns={columns} table_url={"api"} user_id={0} page_name={'testPage'} table_name={'testTable'}/>
</>
);

View File

@@ -1,19 +1,174 @@
import {useMaterialReactTable} from "material-react-table";
import DataTable_Paper from "./table/Paper";
import {useMaterialReactTable} from "material-react-table";
import {FA_DATATABLE_LOCALIZATION} from "./localization/fa/datatable";
import useTableSetting from "@/lib/hooks/useTableSetting";
import {flattenArrayOfObjects} from "@/core/utils/flattenArrayOfObjects";
import Toolbar from "@/components/home/Toolbar";
import useDataTable from "@/lib/hooks/useDataTable";
import {useMemo, useState} from "react";
const isTableInstanceProp = (props) => props.table !== undefined;
const data = [
{
name: {
firstName: 'امین',
lastName: 'قاسمپور',
},
address: 'فلان جا',
city: 'تهران',
state: 'بررسی شده',
price: '20',
updated_at: '2024/05/02',
company: 'همراه اول',
area: 'منطقه 5',
},
{
name: {
firstName: 'امیر حسین',
lastName: 'محمودی',
},
address: 'فلان جا',
city: 'تهران',
state: 'تکمیل شده',
price: '27',
updated_at: '2024/05/02',
company: 'وایتل',
area: 'منطقه 27',
},
{
name: {
firstName: 'یاسمن',
lastName: 'علی اکبری',
},
address: 'فلان جا',
city: 'شیراز',
state: 'بررسی شده',
price: '32',
updated_at: '2024/05/02',
company: 'ایرانسل',
area: 'منطقه 23',
},
{
name: {
firstName: 'حمیدرضا',
lastName: 'رنجبرپور',
},
address: 'فلان جا',
city: 'اصفهان',
state: 'بررسی شده',
price: '45',
updated_at: '2024/05/02',
company: 'ایرانول',
area: 'منطقه 21',
},
{
name: {
firstName: 'محمد',
lastName: 'جلالی',
},
address: 'فلان جا',
city: 'رشت',
state: 'درحال بررسی',
price: '8',
updated_at: '2024/05/02',
company: 'فیلیمو',
area: 'منطقه 8',
},
];
const DataTable_Main = (props) => {
let table;
const {filterData} = useDataTable();
const {table_url, user_id, page_name, table_name, columns, initialStateProps} = props
const flatColumns = flattenArrayOfObjects(columns, 'columns')
const {settingStore, hideAction, sortAction} = useTableSetting();
const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10});
const filters = 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);
const storedFilters = settingStore?.[user_id]?.[page_name]?.[table_name]?.filters;
const finalFilters = storedFilters || filters;
if (isTableInstanceProp(props)) {
table = props.table;
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
table = useMaterialReactTable(props);
const fetchUrl = useMemo(() => {
const params = new URLSearchParams();
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
params.set("size", pagination.pageSize);
params.set("filters", JSON.stringify(finalFilters ?? []));
params.set("sorting", JSON.stringify(settingStore?.[user_id]?.[page_name]?.[table_name]?.['sorts'] ?? []));
return `${table_url}?${params}`;
}, [table_url, finalFilters, pagination, columns]);
const onColumnVisibilityChange = (event) => {
const settingValue = event();
hideAction(user_id, page_name, table_name, settingValue, flatColumns);
};
const onSortingChange = (event) => {
const settingValue = event(settingStore?.[user_id]?.[page_name]?.[table_name]?.['sorts'] || []);
sortAction(user_id, page_name, table_name, settingValue, flatColumns);
}
return <DataTable_Paper table={table} columns={props.columns}/>;
const table = useMaterialReactTable({
localization: FA_DATATABLE_LOCALIZATION,
columns,
data: data,
// manualSorting: true,
renderTopToolbarCustomActions: ({table}) => (<>
<Toolbar
columns={flatColumns}
/>
</>),
initialState: {
density: 'compact',
columnVisibility: settingStore?.[user_id]?.[page_name]?.[table_name]?.['hides'],
sorting: settingStore?.[user_id]?.[page_name]?.[table_name]?.['sorts'] || [],
...initialStateProps
},
state: {
columnVisibility: settingStore?.[user_id]?.[page_name]?.[table_name]?.['hides'],
sorting: settingStore?.[user_id]?.[page_name]?.[table_name]?.['sorts'] || [],
pagination
},
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,
onPaginationChange: setPagination,
manualSorting: true,
onColumnVisibilityChange: onColumnVisibilityChange,
onSortingChange: onSortingChange,
...props
})
return <DataTable_Paper table={table} columns={flatColumns} user_id={user_id} page_name={page_name}
table_name={table_name}/>;
};
export default DataTable_Main;

View File

@@ -1,58 +0,0 @@
import {Grid, TextField} from '@mui/material';
import {useEffect} from "react";
const BetweenNumberFilter = ({formik, id , defaultValue}) => {
useEffect(() => {
formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || '');
formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || '');
}, []);
const handleBlur = (index) => () => {
const minValue = formik.values[id][0];
const maxValue = formik.values[id][1];
if (index === 0 && minValue !== '' && maxValue !== '' && parseInt(minValue) > parseInt(maxValue)) {
formik.setFieldValue(`${id}[0]`, maxValue);
}
if (index === 1 && minValue !== '' && maxValue !== '' && parseInt(maxValue) < parseInt(minValue)) {
formik.setFieldValue(`${id}[1]`, minValue);
}
};
return (
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
name={`${id}[0]`}
label="کمترین رقم"
id={`${id}_min`}
onChange={formik.handleChange}
onBlur={handleBlur(0)}
fullWidth
value={formik.values[id][0] || ''}
margin="normal"
sx={{
position: 'relative'
}}
/>
</Grid>
<Grid item xs={6}>
<TextField
name={`${id}[1]`}
label="بیشترین رقم"
id={`${id}_max`}
onChange={formik.handleChange}
onBlur={handleBlur(1)}
fullWidth
value={formik.values[id][1] || ''}
margin="normal"
sx={{
position: 'relative'
}}
/>
</Grid>
</Grid>
);
};
export default BetweenNumberFilter;

View File

@@ -1,35 +0,0 @@
import {Grid} from '@mui/material';
import MuiDatePicker from "@/core/components/DataTable/filter/DatePicker/MuiDatePicker";
import {useEffect, useState} from "react";
const BetWeenDateFilter = ({formik, id, defaultValue}) => {
useEffect(() => {
formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || '');
formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || '');
}, []);
const [dateRange, setDateRange] = useState(defaultValue);
const handleDateChange = (index, value) => {
const newRange = [...dateRange];
newRange[index] = value;
setDateRange(newRange);
};
return (
<Grid container spacing={1} mt={1} mb={2}>
<Grid item xs={6}>
<MuiDatePicker label="تاریخ شروع " name={`${id}[0]`} formik={formik} defaultValue={defaultValue?.[0]}
minDate={null} maxDate={dateRange[1]}
onDateChange={(value) => handleDateChange(0, value)}/>
</Grid>
<Grid item xs={6}>
<MuiDatePicker label="تاریخ پایان" name={`${id}[1]`} formik={formik} defaultValue={defaultValue?.[1]}
minDate={dateRange[0]} maxDate={null}
onDateChange={(value) => handleDateChange(1, value)}/>
</Grid>
</Grid>
);
};
export default BetWeenDateFilter;

View File

@@ -1,67 +0,0 @@
import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers";
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
import moment from "jalali-moment";
import { faIR } from "@mui/x-date-pickers/locales";
import { Box, IconButton, InputAdornment, TextField } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import { useState } from "react";
export default function MuiDatePicker({label, name, formik, defaultValue, minDate, maxDate, onDateChange }) {
const [pickerValue, setPickerValue] = useState(defaultValue ? new Date(defaultValue) : null);
return (
<Box sx={{ position: 'relative' }}>
<LocalizationProvider
dateAdapter={AdapterDateFnsJalali}
localeText={
faIR.components.MuiLocalizationProvider.defaultProps.localeText
}
>
<MobileDateTimePicker
key={formik.values[name]}
ampm={false}
onChange={(newValue) => {
const date = new Date(newValue);
const formattedDate = moment(date)
.locale("en")
.format("YYYY-MM-DD HH:mm");
formik.setFieldValue(name, formattedDate);
onDateChange(formattedDate);
setPickerValue(newValue);
}}
slotProps={{
textField: {
placeholder: "تاریخ خود را وارد کنید",
helperText: label,
InputProps: {
endAdornment: (
<InputAdornment position="end">
<IconButton
size="small"
onClick={(event) => {
event.stopPropagation();
formik.setFieldValue(name, "");
setPickerValue(null);
}}
sx={{
color: "#bfbfbf",
'&:hover': {
backgroundColor: 'rgba(189, 189, 189, 0.1)',
},
}}
>
<ClearIcon />
</IconButton>
</InputAdornment>
),
},
},
}}
value={pickerValue}
minDate={minDate ? new Date(minDate) : null}
maxDate={maxDate ? new Date(maxDate) : null}
/>
</LocalizationProvider>
</Box>
);
}

View File

@@ -0,0 +1,207 @@
'use client'
import {Box, Button, Drawer, styled} from "@mui/material";
import {Form, Formik} from "formik";
import useTableSetting from "@/lib/hooks/useTableSetting";
import {useEffect, useRef, useState} from "react";
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
import * as Yup from "yup";
import FilterBodyField from "@/core/components/DataTable/filter/FilterBodyField";
import {toast} from "react-toastify";
import AskForKeep from "@/core/components/NotificationDesign/AskForKeep";
import useDataTable from "@/lib/hooks/useDataTable";
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 FilterBody({columns, drawerState, setDrawerState, user_id, page_name, table_name}) {
const {settingStore, filterAction} = useTableSetting();
const [initialValues, setInitialValues] = useState({});
const [validationSchema, setValidationSchema] = useState({});
const {filterData, setFilterData} = useDataTable();
const filterDataRef = useRef(filterData);
useEffect(() => {
filterDataRef.current = filterData;
}, [filterData]);
useEffect(() => {
const values = columns.reduce((acc, column) => {
if (!column.enableColumnFilter) return acc;
const filter = settingStore?.[user_id]?.[page_name]?.[table_name]?.['filters']?.find((filter) => filter.id === column.id);
if (column.datatype === 'array') {
acc[column.id] = {
id: column.id,
value: filter?.value || [],
filterFn: filter?.fn || column._filterFn,
datatype: column.datatype
};
} else if (column._filterFn === "between") {
acc[column.id] = {
id: column.id,
value: filter?.value || ["", ""],
filterFn: filter?.fn || column._filterFn,
datatype: column.datatype
};
} else {
acc[column.id] = {
id: column.id,
value: filter?.value || '',
filterFn: filter?.fn || column._filterFn,
datatype: column.datatype
};
}
return acc;
}, {});
setInitialValues(Object.keys(filterData).length !== 0 ? filterData : values);
}, [columns, settingStore, user_id, page_name, table_name, filterData]);
useEffect(() => {
const generatedSchema = Yup.object().shape(
Object.keys(initialValues).reduce((acc, key) => {
const initialValue = initialValues[key];
if (initialValue.filterFn === "between") {
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(generatedSchema);
}, [initialValues]);
const handleSubmit = (values, {setSubmitting}) => {
toast.dismiss({containerId: "filtering"});
setDrawerState(false);
setFilterData(values);
toast(<AskForKeep onSaveFilter={onSaveFilter}/>, {
containerId: "filtering",
className: "filter-toast",
position: "bottom-left",
draggable: true,
autoClose: false,
});
setSubmitting(false);
};
const onSaveFilter = () => {
const currentFilterData = filterDataRef.current;
const arrayOfObjects = Object.keys(currentFilterData).map(key => {
const value = currentFilterData[key].value;
if (value !== "" &&
!(Array.isArray(value) && (value.length === 0 || (value.length === 2 && value[0] === "" && value[1] === "")))) {
return currentFilterData[key];
}
}).filter(Boolean);
filterAction(user_id, page_name, table_name, arrayOfObjects, columns)
toast.dismiss({containerId: "filtering"});
};
return (
<Drawer open={drawerState} onClose={() => setDrawerState(false)}
sx={{overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%"}}>
<FilterHeader setDrawerState={setDrawerState}/>
{Object.keys(initialValues).length > 0 && (
<ScrollBox>
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
{
({
values,
handleChange,
handleBlur,
setFieldValue,
errors,
touched,
isSubmitting,
isValid,
dirty,
resetForm
}) => (
<Form>
<Box sx={{px: 1, py: 2}}>
{
columns.map((column) => (
column.enableColumnFilter && (
<FilterBodyField
key={column.id}
column={column}
filterParameters={values[column.id]}
handleChange={handleChange}
handleBlur={handleBlur}
setFieldValue={setFieldValue}
resetForm={resetForm}
errors={errors}
touched={touched}
/>
)
))
}
</Box>
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
pb: 2
}}>
<Button
type="submit"
variant="contained"
size="large"
disabled={isSubmitting || !isValid || !dirty}
sx={{
px: 8,
boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
backgroundColor: "#155175", ':hover': {
backgroundColor: "#155175",
}
}}>
اعمال فیلتر
</Button>
</Box>
</Form>
)}
</Formik>
</ScrollBox>
)}
</Drawer>
);
}
export default FilterBody;

View File

@@ -0,0 +1,111 @@
import {useState} from "react";
import FilterOptionList from "@/core/components/DataTable/filter/FilterOptionList";
import CustomSelect from "@/core/components/DataTable/filter/fieldsType/CustomSelect";
import CustomTextField from "@/core/components/DataTable/filter/fieldsType/CustomTextField";
import CustomTextFieldRange from "@/core/components/DataTable/filter/fieldsType/CustomTextFieldRange";
import CustomDatePickerRange from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange";
import CustomDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePicker";
import CustomSelectMultiple from "@/core/components/DataTable/filter/fieldsType/CustomSelectMultiple";
const columnFilterModeOptionFa = {
equals: "برابر",
notEquals: "نابرابر",
contains: "شامل",
lessThan: "کوچکتر",
greaterThan: "بزرگتر",
fuzzy: "فازی",
between: "مابین"
};
function FilterBodyField({
column,
filterParameters,
handleChange,
handleBlur,
setFieldValue,
errors,
touched,
resetForm
}) {
const [anchorEl, setAnchorEl] = useState(null);
const defaultFilterTranslation = columnFilterModeOptionFa[filterParameters.filterFn] || filterParameters.filterFn;
const handleOpenFilterBox = (event) => {
setAnchorEl(event.currentTarget);
};
return (
<>
{filterParameters.datatype === "numeric" && filterParameters.filterFn === "between" ? (
<CustomTextFieldRange
column={column}
filterParameters={filterParameters}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
handleBlur={handleBlur}
handleChange={handleChange}
errors={errors}
touched={touched}
/>
) : filterParameters.datatype === "numeric" && filterParameters.filterFn === "equals" && Array.isArray(column.columnSelectOption) ? (
<CustomSelect
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : filterParameters.datatype === "date" && filterParameters.filterFn === "equals" ? (
<CustomDatePicker
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : filterParameters.datatype === "date" && filterParameters.filterFn === "between" ? (
<CustomDatePickerRange
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
errors={errors}
touched={touched}
/>
) : filterParameters.datatype === "array" && filterParameters.filterFn === "equals" ? (
<CustomSelectMultiple
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : (
<CustomTextField
column={column}
filterParameters={filterParameters}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
handleBlur={handleBlur}
handleChange={(e) => setFieldValue(`${column.id}.value`, e.target.value)}
/>
)}
{Array.isArray(column.columnFilterModeOptions) && (
<FilterOptionList
column={column}
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
filterParameters={filterParameters}
filterType={filterParameters.filterFn}
setFieldValue={setFieldValue}
resetForm={resetForm}
filterOption={column.columnFilterModeOptions}
columnFilterModeOptionFa={columnFilterModeOptionFa}
/>
)}
</>
);
}
export default FilterBodyField;

View File

@@ -0,0 +1,17 @@
'use client'
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import {IconButton, Tooltip} from "@mui/material";
function FilterButton({drawerState, setDrawerState}) {
return (
<Tooltip title="فیلتر">
<IconButton onClick={() => {
setDrawerState(!drawerState)
}} aria-label="filter table">
<FilterAltIcon/>
</IconButton>
</Tooltip>
);
}
export default FilterButton;

View File

@@ -1,201 +0,0 @@
"use client"
import {Box, Button, Divider, Drawer, FormControl, IconButton, styled, Tooltip, Typography} from '@mui/material';
import {useFormik} from 'formik';
import {useEffect, useState} from "react";
import useTableSetting from "@/lib/hooks/useTableSetting";
import BetweenNumberFilter from "@/core/components/DataTable/filter/BetweenNumberFilter";
import BetWeenDateFilter from "@/core/components/DataTable/filter/DatePicker/BetweenDateFilter";
import SelectBoxMultiple from "@/core/components/DataTable/filter/SelectBoxMultiple";
import SelectBox from "@/core/components/DataTable/filter/SelectBox";
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import CloseIcon from '@mui/icons-material/Close';
import TextFieldWithFnBox from "@/core/components/DataTable/filter/TextFieldWithFnBox/TextFieldWithFnBox";
function FilterColumn({columns}) {
const [open, setOpen] = useState(false);
const [selectedFilters, setSelectedFilters] = useState([]);
const [userId, setUserId] = useState(0);
const [pageName, setPageName] = useState('testPage');
const [tableName, setTableName] = useState('testTable');
const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting();
const DrawerHeader = styled('div')(({theme}) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
justifyContent: 'space-between',
}));
const toggleDrawer = (newOpen) => () => {
setOpen(newOpen);
};
const handleDrawerClose = () => {
setOpen(false);
};
useEffect(() => {
const settingValue = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || [];
setSelectedFilters(settingValue.length > 0 ? settingValue : []);
if (!settingValue.length > 0) {
formik.resetForm();
}
}, [settingStore]);
const handleSelectFilter = (fieldName, filterFn) => {
const filterIndex = selectedFilters.findIndex(filter => filter.id === fieldName);
if (filterIndex !== -1) {
const updatedFilters = [...selectedFilters];
updatedFilters[filterIndex].fn = filterFn;
setSelectedFilters(updatedFilters);
} else {
const newFilter = {id: fieldName, fn: filterFn};
const updatedFilters = [...selectedFilters, newFilter];
setSelectedFilters(updatedFilters);
}
};
const initialValues = columns.reduce((acc, column) => {
if (column.enableColumnFilter) {
const filter = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters']?.find((filter) => filter.id === column.id);
if (column.filterFn === 'betweenNum' || column.filterFn === 'betweenDate') {
acc[column.id] = filter?.value || ["", ""];
} else if (column.type === 'select2') {
acc[column.id] = filter?.value || [];
} else {
acc[column.id] = filter?.value || '';
}
}
return acc;
}, {});
const formik = useFormik({
initialValues,
onSubmit: (values) => {
const updatedFilters = selectedFilters.map(filter => {
const value = values[filter.id];
if (value !== undefined) {
return {...filter, value};
}
return filter;
});
Object.entries(values).forEach(([fieldName, value]) => {
const filterIndex = updatedFilters.findIndex(filter => filter.id === fieldName);
if (filterIndex === -1 && value !== '' && value !== null && value !== undefined) {
const column = columns.find(col => col.id === fieldName);
updatedFilters.push({id: fieldName, value, fn: column.filterFn, datatype: column.datatype});
}
});
const newArrayFilter = updatedFilters.filter((filter) => {
const value = values[filter.id];
return (
value !== '' &&
value !== null &&
value !== undefined &&
!(Array.isArray(value) && (value.length === 0 || (value.length === 2 && value.every((v) => v === ''))))
);
});
setSelectedFilters(newArrayFilter);
filterAction(userId, pageName, tableName, newArrayFilter, columns);
}
});
console.log(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'])
return (
<>
<Tooltip title="فیلتر">
<IconButton onClick={toggleDrawer(true)} aria-label="filter table">
<FilterAltIcon/>
</IconButton>
</Tooltip>
<Drawer open={open} onClose={toggleDrawer(false)}
sx={{
"& .MuiDrawer-paper": {
padding: '16px',
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
scrollbarWidth: 'thin',
width: 450,
"&::-webkit-scrollbar": {
width: '8px',
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: '#ccc',
borderRadius: '4px',
},
"@media (max-width: 600px)": {
width: '100%'
},
}
}}
>
<DrawerHeader>
<Typography variant="h6" mb={2}>فیلتر</Typography>
<IconButton onClick={handleDrawerClose}>
<CloseIcon/>
</IconButton>
</DrawerHeader>
<Box sx={{display: 'flex', flexDirection: 'column'}}>
<FormControl fullWidth>
{columns.map((column, index) => (
column.enableColumnFilter &&
(column.inputType === 'betweenDate' ?
<BetWeenDateFilter
key={index}
id={column.id}
formik={formik}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
: column.inputType === 'betweenNum' ?
<BetweenNumberFilter
key={index}
id={column.id}
formik={formik}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ["", ""]}
/>
: column.inputType === 'select2' ?
<SelectBoxMultiple
key={index}
id={column.id}
formik={formik}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || []}
/>
: column.inputType === 'select' ?
<SelectBox
key={index}
id={column.id}
formik={formik}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
:
<TextFieldWithFnBox
key={index}
id={column.id}
header={column.header}
formik={formik}
defaultFilter={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.fn || column._filterFn}
columnFilterModeOptions={column.columnFilterModeOptions}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={formik.values[column.id] || (settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
)
))}
</FormControl>
</Box>
<Box sx={{mt: 2}}>
<Divider/>
<Box sx={{display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
<Button variant="contained" size="medium" onClick={formik.handleSubmit}
sx={{mt: 1}}>ثبت</Button>
</Box>
</Box>
</Drawer>
</>
);
}
export default FilterColumn;

View File

@@ -0,0 +1,32 @@
'use client'
import {Box, IconButton, Typography} from "@mui/material";
import CancelIcon from '@mui/icons-material/Cancel';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
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"}}/>
<Typography variant="h6" sx={{color: "#fff"}}>فیلتر</Typography>
</Box>
<IconButton sx={{color: "#fff"}} onClick={() => setDrawerState(false)}>
<CancelIcon sx={{fontSize: "1em"}}/>
</IconButton>
</Box>
);
}
export default FilterHeader;

View File

@@ -0,0 +1,48 @@
'use client'
import {ListItem, Menu} from "@mui/material";
import {useState} from "react";
function FilterOptionList({
column,
filterType,
filterOption,
anchorEl,
columnFilterModeOptionFa,
setAnchorEl,
setFieldValue
}) {
const [selectedFilter, setSelectedFilter] = useState(filterType);
const handleChangeItem = (event, index) => {
setFieldValue(`${column.id}.value`, filterOption[index] === "between" ? ["", ""] : "");
setFieldValue(`${column.id}.filterFn`, 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;

View File

@@ -1,53 +0,0 @@
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import {useEffect, useState} from "react";
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
'Ralph Hubbard',
'Omar Alexander',
'Carlos Abbott',
'Miriam Wagner',
'Bradley Wilkerson',
'Virginia Andrews',
'Kelly Snyder',
];
const SelectBox = ({ formik, id, defaultValue }) => {
useEffect(() => {
formik.setFieldValue(id, defaultValue || '');
}, []);
const [selectedValue, setSelectedValue] = useState(defaultValue || '');
const handleChange = (event) => {
const { value } = event.target;
setSelectedValue(value);
formik.setFieldValue(id, value);
};
return (
<>
<FormControl sx={{ mt: 3 }}>
<InputLabel>انتخاب</InputLabel>
<Select
id={id}
value={selectedValue}
onChange={handleChange}
label="انتخاب"
>
{names.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</>
);
};
export default SelectBox;

View File

@@ -1,116 +0,0 @@
import {useTheme} from '@mui/material/styles';
import Box from '@mui/material/Box';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import Chip from '@mui/material/Chip';
import {useEffect, useState} from "react";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
'Ralph Hubbard',
'Omar Alexander',
'Carlos Abbott',
'Miriam Wagner',
'Bradley Wilkerson',
'Virginia Andrews',
'Kelly Snyder',
];
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
const SelectBoxMultiple = ({formik, id, defaultValue}) => {
const theme = useTheme();
const [personName, setPersonName] = useState(defaultValue || []);
useEffect(() => {
defaultValue.forEach((value, index) => {
formik.setFieldValue(`${id}[${index}]`, value || '');
});
}, []);
const handleChange = (event) => {
const {
target: {value},
} = event;
const selectedValues = typeof value === 'string' ? value.split(',').filter(item => item.trim() !== '') : value;
formik.setFieldValue(id, []);
if (selectedValues.length === 0) {
setPersonName([]);
formik.setFieldValue(id, []);
} else {
setPersonName(selectedValues);
selectedValues.forEach((selectedValue, index) => {
formik.setFieldValue(`${id}[${index}]`, selectedValue);
});
}
};
return (
<>
<FormControl sx={{width: '100%'}}>
<InputLabel id={id}>انتخاب</InputLabel>
<Select
id={id}
multiple
value={personName}
onChange={handleChange}
input={<OutlinedInput label="Chip"/>}
renderValue={(selected) => (
<Box sx={{display: 'flex', flexWrap: 'wrap', gap: 0.5}}>
{selected.map((value) => (
<Chip key={value} label={value}/>
))}
</Box>
)}
MenuProps={MenuProps}
sx={{
"&.MuiSelect-select": {
minWidth: 0,
maxWidth: '100%',
overflow: 'hidden',
},
}}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</>
);
};
export default SelectBoxMultiple;

View File

@@ -1,46 +0,0 @@
import {ClickAwayListener, List, ListItem, ListItemText, Paper, Popper} from '@mui/material';
import {useState} from "react";
const columnFilterModeOptionFa = {
equals: "برابر",
notEquals: "نابرابر",
contains: "شامل",
lessThan: "کوچکتر",
greaterThan: "بزرگتر",
fuzzy: "فازی",
}
function FilterBox({
anchorEl, open = false, handleClose = () => {
}, columnFilterModeOptions, onSelectFilter, defaultFilter
}) {
const [selectedFilter, setSelectedFilter] = useState(defaultFilter);
const id = open ? 'simple-popper' : undefined;
const handleSelectFilter = (filterFn) => {
setSelectedFilter(filterFn);
onSelectFilter(filterFn);
handleClose();
};
return (
<div>
<Popper id={id} open={open} anchorEl={anchorEl} placement="bottom" style={{zIndex: 1301}}>
<ClickAwayListener onClickAway={handleClose}>
<Paper>
<List>
{columnFilterModeOptions.map((option, index) => (
<ListItem key={index} onClick={() => handleSelectFilter(option)}
selected={option === selectedFilter} sx={{cursor: 'pointer'}}>
<ListItemText primary={columnFilterModeOptionFa[option]}/>
</ListItem>
))}
</List>
</Paper>
</ClickAwayListener>
</Popper>
</div>
);
}
export default FilterBox;

View File

@@ -1,61 +0,0 @@
import {IconButton, TextField} from '@mui/material';
import ArtTrackIcon from '@mui/icons-material/ArtTrack';
import {useEffect, useState} from "react";
import FilterBox from "@/core/components/DataTable/filter/TextFieldWithFnBox/FilterBox";
const TextFieldWithFnBox = ({
formik,
id,
header,
defaultValue,
defaultFilter,
columnFilterModeOptions,
onSelectFilter
}) => {
const [anchorEl, setAnchorEl] = useState(null);
const [open, setOpen] = useState(false);
useEffect(() => {
formik.setFieldValue(id, defaultValue || '');
}, []);
const handleClick = (event) => {
setOpen((prevOpen) => !prevOpen);
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const handleClose = () => {
setOpen(false);
setAnchorEl(null);
};
return (
<>
<TextField
name={id}
label={header}
id={id}
onChange={formik.handleChange}
fullWidth
value={formik.values[id]}
margin="normal"
InputProps={{
endAdornment: (
<IconButton onClick={handleClick}>
<ArtTrackIcon/>
</IconButton>
)
}}
sx={{
position: 'relative'
}}
/>
<FilterBox anchorEl={anchorEl} open={open} handleClose={handleClose}
columnFilterModeOptions={columnFilterModeOptions} onSelectFilter={onSelectFilter}
defaultFilter={defaultFilter}/>
</>
);
};
export default TextFieldWithFnBox;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
import {Typography} from "@mui/material";
function CustomDatePicker({column, filterParameters, defaultFilterTranslation, setFieldValue}) {
return (
<MuiDatePicker
name={`${column.id}.value`}
value={filterParameters.value}
setFieldValue={setFieldValue}
placeholder={column.header}
helperText={
<Typography variant="button" sx={{color: "#155175"}}>
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
</Typography>
}
/>
);
}
export default CustomDatePicker;

View File

@@ -0,0 +1,35 @@
'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, setFieldValue, errors, touched}) {
return (
<Box sx={{display: "flex", gap: 1}}>
<MuiDatePicker
name={`${column.id}.value[0]`}
value={filterParameters.value[0]}
setFieldValue={setFieldValue}
maxDate={filterParameters.value[1]}
placeholder={`از تاریخ`}
helperText={
<Typography variant="button" sx={{color: "#155175"}}>
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
</Typography>
}
/>
<MuiDatePicker
name={`${column.id}.value[1]`}
value={filterParameters.value[1]}
setFieldValue={setFieldValue}
minDate={filterParameters.value[0]}
placeholder={`تا تاریخ`}
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value : null}
error={Boolean(errors?.[`${column.id}`]?.value)}
/>
</Box>
);
}
export default CustomDatePickerRange;

View File

@@ -0,0 +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({value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error}) {
// console.log("error", error);
//
// console.log("helperText", helperText)
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}
name={name}
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,
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>
),
},
},
}}
/>
<FormHelperText id="component-helper-text" sx={{ml: 2, color: error ? '#d32f2f' : "unset"}}>
{helperText ? helperText : ""}
</FormHelperText>
</LocalizationProvider>
</Box>
);
}
export default MuiDatePicker;

View File

@@ -0,0 +1,40 @@
'use client'
import {FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography} from "@mui/material";
function CustomSelect({column, filterParameters, defaultFilterTranslation, setFieldValue}) {
const columnSelectOption = column.columnSelectOption;
return (
<FormControl fullWidth sx={{marginY: 1}}>
<InputLabel id={`label${column.id}`}>{column.header}</InputLabel>
<Select
labelId={`label${column.id}`}
id={column.id}
name={`${column.id}.value`}
value={filterParameters.value}
input={<OutlinedInput label={column.header}/>}
onChange={(e) => {
setFieldValue(`${column.id}.value`, e.target.value)
}}
>
<MenuItem value="">{column.header}</MenuItem>
{columnSelectOption.map((option) => (
<MenuItem
key={option.value}
value={option.value}
>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>
<Typography variant="button" sx={{color: "#155175"}}>
نوع فیلتر: {defaultFilterTranslation}
</Typography>
</FormHelperText>
</FormControl>
);
}
export default CustomSelect;

View File

@@ -0,0 +1,62 @@
'use client'
import {
Box,
Chip,
FormControl,
FormHelperText,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Typography
} from "@mui/material";
function CustomSelectMultiple({column, filterParameters, defaultFilterTranslation, setFieldValue}) {
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}}>
<InputLabel htmlFor="uncontrolled-native"
id={`label${column.id}`}>{column.header}</InputLabel>
<Select
labelId={`label${column.id}`}
id={column.id}
value={filterParameters.value}
multiple
onChange={(e) => {
setFieldValue(`${column.id}.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}/>}
>
{columnSelectOption.map((option) => (
<MenuItem
key={option.value}
value={option.value}
>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>
<Typography variant="button" sx={{color: "#155175"}}>
نوع فیلتر: {defaultFilterTranslation}
</Typography>
</FormHelperText>
</FormControl>
);
}
export default CustomSelectMultiple;

View File

@@ -0,0 +1,39 @@
import {InputAdornment, TextField, Typography} from "@mui/material";
import FilterListIcon from '@mui/icons-material/FilterList';
function CustomTextField({
column,
filterParameters,
defaultFilterTranslation,
handleOpenFilterBox,
handleChange,
handleBlur
}) {
return (
<TextField
id={column.id}
name={column.id}
label={column.header}
value={filterParameters.value}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
helperText={<Typography variant="button" sx={{color: "#155175"}}>
نوع فیلتر: {defaultFilterTranslation}
</Typography>}
variant="outlined"
size="normal"
sx={{marginY: 1}}
InputProps={{
endAdornment: (
<InputAdornment position="end" sx={{cursor: "pointer"}} onClick={handleOpenFilterBox}>
<FilterListIcon/>
</InputAdornment>
),
}}
/>
);
}
export default CustomTextField;

View File

@@ -0,0 +1,56 @@
import {Box, InputAdornment, TextField, Typography} from "@mui/material";
import FilterListIcon from '@mui/icons-material/FilterList';
function CustomTextFieldRange({
column,
filterParameters,
defaultFilterTranslation,
handleOpenFilterBox,
handleChange,
handleBlur,
errors,
touched
}) {
return (
<Box sx={{display: "flex"}}>
<TextField
id={`${column.id}.value[0]`}
name={`${column.id}.value[0]`}
onChange={handleChange}
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="button" sx={{color: "#155175"}}>نوع
فیلتر: {defaultFilterTranslation}</Typography>}
variant="outlined"
size="normal"
sx={{marginY: 1, marginRight: 1}}
/>
<TextField
id={`${column.id}.value[1]`}
name={`${column.id}.value[1]`}
onChange={handleChange}
onBlur={handleBlur}
error={touched?.[`${column.id}`]?.value && Boolean(errors?.[`${column.id}`]?.value)}
helperText={touched?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value : null}
label={<Typography>تا {column.header}</Typography>}
value={filterParameters.value[1]}
fullWidth
variant="outlined"
size="normal"
sx={{marginY: 1}}
InputProps={{
endAdornment: (
<InputAdornment position="end" sx={{cursor: "pointer"}} onClick={handleOpenFilterBox}>
<FilterListIcon/>
</InputAdornment>
),
}}
/>
</Box>
);
}
export default CustomTextFieldRange;

View File

@@ -0,0 +1,19 @@
'use client'
import {useState} from "react";
import FilterButton from "@/core/components/DataTable/filter/FilterButton";
import FilterBody from "@/core/components/DataTable/filter/FilterBody";
function FilterColumn({columns, user_id, page_name, table_name}) {
const [open, setOpen] = useState(false);
return (
<>
<FilterButton drawerState={open} setDrawerState={setOpen}/>
<FilterBody columns={columns} drawerState={open} setDrawerState={setOpen} user_id={user_id}
page_name={page_name} table_name={table_name}/>
</>
);
}
export default FilterColumn;

View File

@@ -1,142 +1,12 @@
import {useMaterialReactTable} from "material-react-table";
import DataTable_Main from "./Main";
import {FA_DATATABLE_LOCALIZATION} from "./localization/fa/datatable";
import useTableSetting from "@/lib/hooks/useTableSetting";
import {flattenArrayOfObjects} from "@/core/utils/flattenArrayOfObjects";
import Toolbar from "@/components/home/Toolbar";
const data = [
{
name: {
firstName: 'امین',
lastName: 'قاسمپور',
},
address: 'فلان جا',
city: 'تهران',
state: 'بررسی شده',
price: '20',
updated_at: '2024/05/02',
company: 'همراه اول',
area: 'منطقه 5',
},
{
name: {
firstName: 'امیر حسین',
lastName: 'محمودی',
},
address: 'فلان جا',
city: 'تهران',
state: 'تکمیل شده',
price: '27',
updated_at: '2024/05/02',
company: 'وایتل',
area: 'منطقه 27',
},
{
name: {
firstName: 'یاسمن',
lastName: 'علی اکبری',
},
address: 'فلان جا',
city: 'شیراز',
state: 'بررسی شده',
price: '32',
updated_at: '2024/05/02',
company: 'ایرانسل',
area: 'منطقه 23',
},
{
name: {
firstName: 'حمیدرضا',
lastName: 'رنجبرپور',
},
address: 'فلان جا',
city: 'اصفهان',
state: 'بررسی شده',
price: '45',
updated_at: '2024/05/02',
company: 'ایرانول',
area: 'منطقه 21',
},
{
name: {
firstName: 'محمد',
lastName: 'جلالی',
},
address: 'فلان جا',
city: 'رشت',
state: 'درحال بررسی',
price: '8',
updated_at: '2024/05/02',
company: 'فیلیمو',
area: 'منطقه 8',
},
];
import DataTableProvider from "@/lib/contexts/DataTable";
const DataTable = (props) => {
const {userId, pageName, tableName, columns, initialStateProps} = props
const {settingStore, hideAction, sortAction} = useTableSetting();
const flatColumns = flattenArrayOfObjects(columns, 'columns')
const onColumnVisibilityChange = (event) => {
const settingValue = event();
hideAction(userId, pageName, tableName, settingValue, flatColumns);
};
const onSortingChange = (event) => {
const settingValue = event(settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []);
sortAction(userId, pageName, tableName, settingValue, flatColumns);
}
const table = useMaterialReactTable({
localization: FA_DATATABLE_LOCALIZATION,
columns,
data: data,
// manualSorting: true,
renderTopToolbarCustomActions: ({table}) => (<>
<Toolbar
columns={flatColumns}
/>
</>),
initialState: {
density: 'compact',
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || [],
...initialStateProps
},
state: {
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
},
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"
}
},
},
onColumnVisibilityChange: onColumnVisibilityChange,
onSortingChange: onSortingChange,
...props
})
return <DataTable_Main table={table} columns={flatColumns}/>;
return (
<DataTableProvider>
<DataTable_Main {...props}/>
</DataTableProvider>
);
};
export default DataTable;
export default DataTable;

View File

@@ -6,9 +6,9 @@ import {useState} from "react";
function RefactorTable() {
const {refactorAction} = useTableSetting();
const [userId, setUserId] = useState(2);
const [pageName, setPageName] = useState('loan');
const [tableName, setTableName] = useState('loan_table');
const [userId, setUserId] = useState(0);
const [pageName, setPageName] = useState('testPage');
const [tableName, setTableName] = useState('testTable');
const refactor = () => {
refactorAction(userId, pageName, tableName);

View File

@@ -65,7 +65,10 @@ const DataTable_Paper = ({table, ...rest}) => {
>
{enableTopToolbar &&
(parseFromValuesOrFunc(renderTopToolbar, {table}) ?? (
<DataTable_TopToolbar table={table} columns={paperProps.columns}/>
<DataTable_TopToolbar table={table} columns={paperProps.columns} table_url={paperProps.table_url}
user_id={paperProps.user_id}
page_name={paperProps.page_name}
table_name={paperProps.table_name}/>
))}
<DataTable_TableContainer table={table}/>
{enableBottomToolbar &&

View File

@@ -3,10 +3,10 @@ import {Box, useMediaQuery} from "@mui/material";
import DataTable_LinearProgressBar from "./LinearProgressBar";
import DataTable_TablePagination from "./TablePagination";
import DataTable_ToolbarInternalButtons from "./ToolbarInternalButtons";
import FilterColumn from "@/core/components/DataTable/filter/FilterColumn";
import FilterColumn from "@/core/components/DataTable/filter";
import RefactorTable from "@/core/components/DataTable/refactor/RefactorTable";
const DataTable_TopToolbar = ({table, columns}) => {
const DataTable_TopToolbar = ({table, columns, table_url, user_id, page_name, table_name}) => {
const {
getState,
options: {
@@ -73,7 +73,8 @@ const DataTable_TopToolbar = ({table, columns}) => {
}}
>
<RefactorTable/>
<FilterColumn columns={columns}/>
<FilterColumn columns={columns} table_url={table_url} user_id={user_id} page_name={page_name}
table_name={table_name}/>
<DataTable_ToolbarInternalButtons table={table}/>
</Box>
)}

View File

@@ -0,0 +1,47 @@
'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";
function AskForKeep({onSaveFilter}) {
const handleDismiss = () => {
toast.dismiss({containerId: "filtering"});
};
return (
<Box sx={{width: "100%"}}>
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between", mb: 1}}>
<Typography color="#155175" variant="subtitle1" sx={{fontWeight: "500"}}>
ذخیره سازی اطلاعات
</Typography>
<SaveIcon sx={{color: "#155175"}}/>
</Box>
<Box sx={{display: "flex", flexDirection: "column"}}>
<Typography variant="caption">
آیا مایل به ذخیره فیلتر های اعمال شده برای دفعات بعد هستید؟
</Typography>
</Box>
<Box sx={{display: "flex", gap: 1, mt: 1}}>
<Button variant="contained" onClick={handleDismiss} sx={{
backgroundColor: "#792626",
':hover': {backgroundColor: "#792626"},
boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
}}>رد
کردن</Button>
<Button variant="contained" component="label" endIcon={<DownloadIcon/>}
onClick={onSaveFilter}
sx={{
backgroundColor: "#155175",
':hover': {backgroundColor: "#155175"},
boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
}}>ذخیره
فیلتر</Button>
</Box>
</Box>
);
}
export default AskForKeep;

View File

@@ -1,5 +1,5 @@
'use client'
import { createTheme } from "@mui/material";
import {createTheme} from "@mui/material";
const theme = createTheme({
direction: 'rtl',

View File

@@ -0,0 +1,15 @@
import {createContext, useState} from "react";
export const DataTableContext = createContext();
const DataTableProvider = ({children}) => {
const [filterData, setFilterData] = useState({});
return (
<DataTableContext.Provider value={{filterData, setFilterData}}>
{children}
</DataTableContext.Provider>
);
};
export default DataTableProvider;

View File

@@ -0,0 +1,9 @@
import {useContext} from "react";
import {DataTableContext} from "@/lib/contexts/DataTable";
const useTableSetting = () => {
const {filterData, setFilterData} = useContext(DataTableContext);
return {filterData, setFilterData};
};
export default useTableSetting;