Merge branch 'feature/mohammad_table_test' into 'develop'
Feature/mohammad table test See merge request witel-front-end/rms!1
This commit is contained in:
@@ -15,6 +15,13 @@
|
|||||||
"@mui/icons-material": "^5.15.6",
|
"@mui/icons-material": "^5.15.6",
|
||||||
"@mui/material": "^5.15.6",
|
"@mui/material": "^5.15.6",
|
||||||
"@mui/material-nextjs": "^5.15.6",
|
"@mui/material-nextjs": "^5.15.6",
|
||||||
|
"@mui/x-date-pickers": "^6.19.5",
|
||||||
|
"date-fns": "^3.3.1",
|
||||||
|
"date-fns-jalali": "^2.13.0-0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"formik": "^2.4.5",
|
||||||
|
"jalali-moment": "^3.3.11",
|
||||||
|
"material-react-table": "^2.11.2",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
import {TableSettingProvider} from "@/lib/contexts/tableSetting";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'سامانه جامع راهداری',
|
title: 'سامانه جامع راهداری',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({children}) {
|
export default function RootLayout({children}) {
|
||||||
return (<html lang="fa" dir={'rtl'}>
|
return (
|
||||||
|
<html lang="fa" dir={'rtl'}>
|
||||||
<body style={{height: '100vh'}}>
|
<body style={{height: '100vh'}}>
|
||||||
{children}
|
<TableSettingProvider>
|
||||||
|
{children}
|
||||||
|
</TableSettingProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>)
|
</html>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
const Page = () => {
|
import DataTableMain from "@/components/DataTableMain";
|
||||||
return (<></>)
|
|
||||||
|
function page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DataTableMain />
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page
|
export default page;
|
||||||
|
|||||||
31
src/components/BetweenDateFilter.jsx
Normal file
31
src/components/BetweenDateFilter.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {Grid} from '@mui/material';
|
||||||
|
import MuiDatePicker from "@/components/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;
|
||||||
58
src/components/BetweenNumberFilter.jsx
Normal file
58
src/components/BetweenNumberFilter.jsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
20
src/components/CustomToolbar.jsx
Normal file
20
src/components/CustomToolbar.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import FilterColumn from "@/components/FilterColumn";
|
||||||
|
|
||||||
|
const CustomToolbar = ({columns}) => {
|
||||||
|
return (
|
||||||
|
<FilterColumn columns={columns}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CustomToolbar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
141
src/components/DataTableMain.jsx
Normal file
141
src/components/DataTableMain.jsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import DataTableTest from "@/components/DataTableTest";
|
||||||
|
import FilterColumn from "@/components/FilterColumn";
|
||||||
|
import {useMemo} from "react";
|
||||||
|
import {Button} from "@mui/material";
|
||||||
|
|
||||||
|
function DataTableMain() {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'name.firstName',
|
||||||
|
header: 'نام',
|
||||||
|
size: 150,
|
||||||
|
id: "firstName",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "contains",
|
||||||
|
columnFilterModeOptions: [
|
||||||
|
"equals",
|
||||||
|
"notEquals",
|
||||||
|
"contains"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name.lastName',
|
||||||
|
header: 'نام خانوادگی',
|
||||||
|
size: 150,
|
||||||
|
id: "lastName",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "contains",
|
||||||
|
columnFilterModeOptions: [
|
||||||
|
"equals",
|
||||||
|
"notEquals",
|
||||||
|
"contains"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'address',
|
||||||
|
header: 'آدرس',
|
||||||
|
size: 200,
|
||||||
|
id: "address",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "contains",
|
||||||
|
columnFilterModeOptions: [
|
||||||
|
"equals",
|
||||||
|
"notEquals",
|
||||||
|
"contains"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'city',
|
||||||
|
header: 'شهر',
|
||||||
|
size: 150,
|
||||||
|
id: "city",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "contains",
|
||||||
|
columnFilterModeOptions: [
|
||||||
|
"equals",
|
||||||
|
"notEquals",
|
||||||
|
"contains",
|
||||||
|
"lessThan",
|
||||||
|
"greaterThan",
|
||||||
|
"between",
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'state',
|
||||||
|
header: 'وضعیت',
|
||||||
|
size: 150,
|
||||||
|
id: "state",
|
||||||
|
text:'نام',
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "contains",
|
||||||
|
columnFilterModeOptions: [
|
||||||
|
"equals",
|
||||||
|
"notEquals",
|
||||||
|
"contains"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'state',
|
||||||
|
header: 'محدوده',
|
||||||
|
size: 150,
|
||||||
|
id: "betweenNum",
|
||||||
|
text:'محدوده',
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "betweenNum",
|
||||||
|
columnFilterModeOptions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'state',
|
||||||
|
header: 'تاریخ',
|
||||||
|
size: 150,
|
||||||
|
id: "betweenDate",
|
||||||
|
text:'تاریخ',
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "betweenDate",
|
||||||
|
columnFilterModeOptions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'state',
|
||||||
|
header: 'شلکت',
|
||||||
|
size: 150,
|
||||||
|
id: "select2",
|
||||||
|
text:'تاریخ',
|
||||||
|
type: 'select2',
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "equals",
|
||||||
|
columnFilterModeOptions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'state',
|
||||||
|
header: 'سلکت',
|
||||||
|
size: 150,
|
||||||
|
id: "select",
|
||||||
|
text:'تنتنت',
|
||||||
|
type: 'select',
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterFn: "equals",
|
||||||
|
columnFilterModeOptions: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DataTableTest columns={columns}/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataTableMain;
|
||||||
104
src/components/DataTableTest.jsx
Normal file
104
src/components/DataTableTest.jsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"use client"
|
||||||
|
import {useMemo, useState} from 'react';
|
||||||
|
import {MaterialReactTable} from 'material-react-table';
|
||||||
|
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||||
|
import {IconButton, Tooltip, Typography} from "@mui/material";
|
||||||
|
import CustomToolbar from "@/components/CustomToolbar";
|
||||||
|
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
},
|
||||||
|
address: '261 Erdman Ford',
|
||||||
|
city: 'East Daphne',
|
||||||
|
state: 'Kentucky',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
firstName: 'Jane',
|
||||||
|
lastName: 'Doe',
|
||||||
|
},
|
||||||
|
address: '769 Dominic Grove',
|
||||||
|
city: 'Columbus',
|
||||||
|
state: 'Ohio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
firstName: 'Joe',
|
||||||
|
lastName: 'Doe',
|
||||||
|
},
|
||||||
|
address: '566 Brakus Inlet',
|
||||||
|
city: 'South Linda',
|
||||||
|
state: 'West Virginia',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
firstName: 'Kevin',
|
||||||
|
lastName: 'Vandy',
|
||||||
|
},
|
||||||
|
address: '722 Emie Stream',
|
||||||
|
city: 'Lincoln',
|
||||||
|
state: 'Nebraska',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
firstName: 'Joshua',
|
||||||
|
lastName: 'Rolluffs',
|
||||||
|
},
|
||||||
|
address: '32188 Larkin Turnpike',
|
||||||
|
city: 'Charleston',
|
||||||
|
state: 'South Carolina',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function RefreshIcon() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestDataTable = ({columns}) => {
|
||||||
|
const [userId, setUserId] = useState(2);
|
||||||
|
const [pageName, setPageName] = useState('loan');
|
||||||
|
const [tableName, setTableName] = useState('loan_table');
|
||||||
|
const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting();
|
||||||
|
|
||||||
|
const onColumnVisibilityChange = (event) => {
|
||||||
|
const settingValue = event();
|
||||||
|
hideAction(userId, pageName, tableName, settingValue, columns);
|
||||||
|
};
|
||||||
|
const onSortingChange = (event) => {
|
||||||
|
const settingValue = event(settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []);
|
||||||
|
sortAction(userId, pageName, tableName, settingValue, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography>{settingStore?.[userId]?.[pageName]?.[tableName]?.['summary']}</Typography>
|
||||||
|
<MaterialReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
manualSorting={true}
|
||||||
|
renderTopToolbarCustomActions={({table}) => (<>
|
||||||
|
<CustomToolbar
|
||||||
|
columns={columns}
|
||||||
|
/>
|
||||||
|
</>)}
|
||||||
|
initialState={{
|
||||||
|
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
|
||||||
|
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
|
||||||
|
}}
|
||||||
|
state={{
|
||||||
|
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
|
||||||
|
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
|
||||||
|
}}
|
||||||
|
onColumnVisibilityChange={onColumnVisibilityChange}
|
||||||
|
onSortingChange={onSortingChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestDataTable;
|
||||||
|
|
||||||
30
src/components/FilterBox.jsx
Normal file
30
src/components/FilterBox.jsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Popper, Paper, List, ListItem, ListItemText, ClickAwayListener } from '@mui/material';
|
||||||
|
|
||||||
|
function FilterBox({anchorEl , open = false, handleClose = () => {} , columnFilterModeOptions , onSelectFilter , defaultFilter}) {
|
||||||
|
|
||||||
|
const id = open ? 'simple-popper' : undefined;
|
||||||
|
const handleSelectFilter = (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 === defaultFilter} sx={{ cursor: 'pointer' }}>
|
||||||
|
<ListItemText primary={option} />
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Popper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterBox;
|
||||||
206
src/components/FilterColumn.jsx
Normal file
206
src/components/FilterColumn.jsx
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
"use client"
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button, Divider, Drawer,
|
||||||
|
FormControl, IconButton, styled, Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
import {useFormik} from 'formik';
|
||||||
|
import TextFieldWithFnBox from "@/components/TextFieldWithFnBox";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||||
|
import BetweenNumberFilter from "@/components/BetweenNumberFilter";
|
||||||
|
import BetWeenDateFilter from "@/components/BetweenDateFilter";
|
||||||
|
import SelectBoxMultiple from "@/components/SelectBoxMultiple";
|
||||||
|
import SelectBox from "@/components/SelectBox";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
function FilterColumn({ columns }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedFilters, setSelectedFilters] = useState([]);
|
||||||
|
const [userId, setUserId] = useState(2);
|
||||||
|
const [pageName, setPageName] = useState('loan');
|
||||||
|
const [tableName, setTableName] = useState('loan_table');
|
||||||
|
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 : []);
|
||||||
|
}, [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) => {
|
||||||
|
console.log(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={toggleDrawer(true)}>Open drawer</Button>
|
||||||
|
<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.filterFn === 'betweenDate' ?
|
||||||
|
<BetWeenDateFilter
|
||||||
|
key={index}
|
||||||
|
id={column.id}
|
||||||
|
formik={formik}
|
||||||
|
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
|
||||||
|
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
|
||||||
|
/>
|
||||||
|
: column.filterFn === 'betweenNum' ?
|
||||||
|
<BetweenNumberFilter
|
||||||
|
key={index}
|
||||||
|
id={column.id}
|
||||||
|
formik={formik}
|
||||||
|
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
|
||||||
|
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ["", ""]}
|
||||||
|
/>
|
||||||
|
: column.type === 'select2' ?
|
||||||
|
<SelectBoxMultiple
|
||||||
|
key={index}
|
||||||
|
id={column.id}
|
||||||
|
formik={formik}
|
||||||
|
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
|
||||||
|
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || []}
|
||||||
|
/>
|
||||||
|
: column.type === 'select' ?
|
||||||
|
<SelectBox
|
||||||
|
key={index}
|
||||||
|
id={column.id}
|
||||||
|
formik={formik}
|
||||||
|
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
41
src/components/MapTest.jsx
Normal file
41
src/components/MapTest.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"use client"
|
||||||
|
import {Typography} from "@mui/material";
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id : "hello",
|
||||||
|
Subitems : [
|
||||||
|
{id : "amin",firstName : "amin", lastName:"ali",Subitems : [{id : "shahrokh",shahrokh : "shahrokh"}], hasSubitems : true, showSubitems : false},
|
||||||
|
{id : "amir",firstName : "amir", lastName:"akbar", hasSubitems : false, showSubitems : false},
|
||||||
|
],
|
||||||
|
hasSubitems : true,
|
||||||
|
showSubitems : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : "hi",
|
||||||
|
Subitems : [
|
||||||
|
{id : "ali",firstName : "ali", lastName:"ali", hasSubitems : false, showSubitems : false},
|
||||||
|
{id : "akbar",firstName : "akbar", lastName:"akbar", hasSubitems : false, showSubitems : false},
|
||||||
|
],
|
||||||
|
hasSubitems : true,
|
||||||
|
showSubitems : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const MapTest = () => {
|
||||||
|
const Amin = data.map((item) => {
|
||||||
|
return item.hasSubitems ? {...item, Subitems: item.Subitems.map((subitem) => true ? {
|
||||||
|
...subitem, showSubitems: !item.showSubitems
|
||||||
|
} : subitem)} : item
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
console.log(Amin)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography>hello</Typography>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default MapTest
|
||||||
63
src/components/MuiDatePicker.jsx
Normal file
63
src/components/MuiDatePicker.jsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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 } 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={{ padding: '4px', position: 'relative' }}>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={pickerValue}
|
||||||
|
minDate={minDate ? new Date(minDate) : null}
|
||||||
|
maxDate={maxDate ? new Date(maxDate) : null}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Box>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
formik.setFieldValue(name, "");
|
||||||
|
setPickerValue(null);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
position: 'absolute',
|
||||||
|
right: '4px',
|
||||||
|
top: '35%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
src/components/SelectBox.jsx
Normal file
53
src/components/SelectBox.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
113
src/components/SelectBoxMultiple.jsx
Normal file
113
src/components/SelectBoxMultiple.jsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
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;
|
||||||
|
console.log(value)
|
||||||
|
const selectedValues = typeof value === 'string' ? value.split(',') : value;
|
||||||
|
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;
|
||||||
51
src/components/TextFieldWithFnBox.jsx
Normal file
51
src/components/TextFieldWithFnBox.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {IconButton, Paper, TextField} from '@mui/material';
|
||||||
|
import ArtTrackIcon from '@mui/icons-material/ArtTrack';
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import FilterBox from "@/components/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;
|
||||||
@@ -4,6 +4,7 @@ import {Box, Drawer, IconButton, Paper, styled, Toolbar, Typography} from "@mui/
|
|||||||
import MuiAppBar from '@mui/material/AppBar';
|
import MuiAppBar from '@mui/material/AppBar';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
|
import SidebarMenu from "@/core/components/SidebarMenu";
|
||||||
|
|
||||||
const drawerWidth = 260;
|
const drawerWidth = 260;
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ const HeaderWithSidebar = ({children}) => {
|
|||||||
<ChevronRightIcon/>
|
<ChevronRightIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
|
<SidebarMenu />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Main elevation={0} open={open}>
|
<Main elevation={0} open={open}>
|
||||||
<DrawerHeader/>
|
<DrawerHeader/>
|
||||||
|
|||||||
46
src/core/components/SidebarListItems.jsx
Normal file
46
src/core/components/SidebarListItems.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {Collapse, ListItem, ListItemButton, ListItemIcon, ListItemText} from "@mui/material";
|
||||||
|
import { ExpandLess, ExpandMore } from '@mui/icons-material';
|
||||||
|
import WifiIcon from '@mui/icons-material/Wifi';
|
||||||
|
import SidebarSubitems from "@/core/components/SidebarSubitems";
|
||||||
|
|
||||||
|
const SidebarListItems = ({menuItem, dispatch}) => {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<ListItem disablePadding sx={{p : 0}}>
|
||||||
|
<ListItemButton sx={{
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
},
|
||||||
|
}} selected={menuItem.selected} onClick={() => {
|
||||||
|
dispatch({type: "COLLAPSE_MENU", id: menuItem.id})
|
||||||
|
}}>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 0,
|
||||||
|
justifyContent: "center",
|
||||||
|
color: "primary.main",
|
||||||
|
width: 40,
|
||||||
|
height: 24,
|
||||||
|
pr: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{menuItem.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={menuItem.id} />
|
||||||
|
{menuItem.hasSubitems ? (menuItem.showSubitems ? <ExpandLess /> : <ExpandMore />) : null }
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={menuItem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
|
{
|
||||||
|
menuItem.hasSubitems ? (menuItem.Subitems.map((subitem,index)=>{
|
||||||
|
return(
|
||||||
|
<SidebarSubitems dispatch={dispatch} key={index} subitem={subitem}/>
|
||||||
|
)
|
||||||
|
})) : null
|
||||||
|
}
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default SidebarListItems
|
||||||
201
src/core/components/SidebarMenu.jsx
Normal file
201
src/core/components/SidebarMenu.jsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
"use client"
|
||||||
|
import {List} from "@mui/material";
|
||||||
|
import SidebarListItems from "@/core/components/SidebarListItems";
|
||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {usePathname} from 'next/navigation'
|
||||||
|
import SecurityIcon from '@mui/icons-material/Security';
|
||||||
|
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
||||||
|
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||||
|
import AirlineSeatReclineNormalIcon from "@mui/icons-material/AirlineSeatReclineNormal";
|
||||||
|
import RepartitionIcon from '@mui/icons-material/Repartition';
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id: "hello",
|
||||||
|
type: "menu",
|
||||||
|
route: "/hello",
|
||||||
|
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
selected: false,
|
||||||
|
Subitems: [
|
||||||
|
{
|
||||||
|
id: "amin",
|
||||||
|
route: "/s",
|
||||||
|
icon: <SpaceDashboardIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
firstName: "amin",
|
||||||
|
lastName: "ali",
|
||||||
|
Subitems: [
|
||||||
|
{
|
||||||
|
id: "shahrokh", shahrokh: "shahrokh", type: "page", selected: false, route: "/dashbssoard", icon: <AssessmentIcon sx={{width: 'inherit', height: 'inherit'}}/>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shasdasdsahrokh", shahrokh: "shahroasdasdkh", type: "page", selected: false, route: "/dashboardsd", icon: <AirlineSeatReclineNormalIcon sx={{width: 'inherit', height: 'inherit'}}/>
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hasSubitems: true,
|
||||||
|
showSubitems: false,
|
||||||
|
type: "menu",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "amir",
|
||||||
|
route: "/dashboarsad",
|
||||||
|
icon: <RepartitionIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
firstName: "amir",
|
||||||
|
lastName: "akbar",
|
||||||
|
hasSubitems: false,
|
||||||
|
showSubitems: false,
|
||||||
|
selected: false,
|
||||||
|
type: "page",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hasSubitems: true,
|
||||||
|
showSubitems: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "hi",
|
||||||
|
type: "menu",
|
||||||
|
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
route: "/hi",
|
||||||
|
selected: false,
|
||||||
|
Subitems: [
|
||||||
|
{
|
||||||
|
id: "ali",
|
||||||
|
route: "/dashboardss",
|
||||||
|
firstName: "ali",
|
||||||
|
lastName: "ali",
|
||||||
|
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
hasSubitems: false,
|
||||||
|
showSubitems: false,
|
||||||
|
type: "page",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "akbar",
|
||||||
|
route: "/akbar",
|
||||||
|
firstName: "akbar",
|
||||||
|
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
lastName: "akbar",
|
||||||
|
hasSubitems: false,
|
||||||
|
showSubitems: false,
|
||||||
|
type: "page",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hasSubitems: true,
|
||||||
|
showSubitems: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "hoo",
|
||||||
|
type: "page",
|
||||||
|
route: "/dashboard",
|
||||||
|
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||||
|
selected: false,
|
||||||
|
hasSubitems: false,
|
||||||
|
showSubitems: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
function reducer(state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case "COLLAPSE_MENU":
|
||||||
|
return state.map((item) => action.id == item.id ? {
|
||||||
|
...item, showSubitems: !item.showSubitems
|
||||||
|
} : item)
|
||||||
|
case "COLLAPSE_SUB_ITEMS" :
|
||||||
|
return state.map((item) => {
|
||||||
|
return item.hasSubitems ? {
|
||||||
|
...item, Subitems: item.Subitems.map((subitem) => action.id === subitem.id ? {
|
||||||
|
...subitem, showSubitems: !subitem.showSubitems
|
||||||
|
} : subitem)
|
||||||
|
} : item
|
||||||
|
})
|
||||||
|
case "COLLAPSE_SUB_SECOND_ITEMS" :
|
||||||
|
return state.map((item) => {
|
||||||
|
return item.hasSubitems ? {
|
||||||
|
...item, Subitems: item.Subitems.map((subitem) => {
|
||||||
|
return subitem.hasSubitems ? {
|
||||||
|
...subitem,
|
||||||
|
Subitems: subitem.Subitems.map((secondSubitem) => action.id === secondSubitem.id ? {
|
||||||
|
...secondSubitem, showSubitems: !secondSubitem.showSubitems
|
||||||
|
} : secondSubitem)
|
||||||
|
} : subitem
|
||||||
|
})
|
||||||
|
} : item
|
||||||
|
})
|
||||||
|
case "SELECTED":
|
||||||
|
return state.map((item) => {
|
||||||
|
return item.type === "page" ? {
|
||||||
|
...item, selected: action.route === item.route,showSubitems: item.route === action.route
|
||||||
|
} : item.Subitems && Array.isArray(item.Subitems) ? {
|
||||||
|
...item, Subitems: item.Subitems.map((subitem) => {
|
||||||
|
return subitem.type === "page" ? {
|
||||||
|
...subitem, selected: action.route === subitem.route,showSubitems: subitem.route === action.route
|
||||||
|
} : subitem.Subitems && Array.isArray(subitem.Subitems) ? {
|
||||||
|
...subitem, Subitems: subitem.Subitems.map((secondSubItem) => ({
|
||||||
|
...secondSubItem, selected: secondSubItem.route === action.route
|
||||||
|
})),showSubitems: subitem.Subitems.some((secondSubItem) => secondSubItem.route === action.route),
|
||||||
|
} : subitem
|
||||||
|
}),showSubitems: item.Subitems.some((subitem) => subitem.showSubitems),
|
||||||
|
} : item
|
||||||
|
});
|
||||||
|
// case "SELECTED":
|
||||||
|
// return state.map((item) => {
|
||||||
|
// return item.type === "page" ? {
|
||||||
|
// ...item,
|
||||||
|
// selected: action.route === item.route
|
||||||
|
// } : item.Subitems && Array.isArray(item.Subitems) ? {
|
||||||
|
// ...item,
|
||||||
|
// Subitems: item.Subitems.map((subitem) => {
|
||||||
|
// const updatedSubitem = subitem.type === "page" ? {
|
||||||
|
// ...subitem,
|
||||||
|
// selected: action.route === subitem.route
|
||||||
|
// } : subitem.Subitems && Array.isArray(subitem.Subitems) ? {
|
||||||
|
// ...subitem,
|
||||||
|
// Subitems: subitem.Subitems.map((secondSubItem) => ({
|
||||||
|
// ...secondSubItem,
|
||||||
|
// selected: secondSubItem.route === action.route
|
||||||
|
// })),
|
||||||
|
// showSubitems: subitem.Subitems.some((secondSubItem) => secondSubItem.route === action.route),
|
||||||
|
// } : subitem;
|
||||||
|
//
|
||||||
|
// console.log("Updated subitem:", updatedSubitem); // Log the updated subitem
|
||||||
|
// return updatedSubitem;
|
||||||
|
// }),
|
||||||
|
// showSubitems: item.Subitems.some((subitem) => subitem.showSubitems),
|
||||||
|
// } : item
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarMenu = () => {
|
||||||
|
const [menuItems, dispatch] = useReducer(reducer, data);
|
||||||
|
const pathname = usePathname()
|
||||||
|
const [selectedKey, setSelectedKey] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({type: "SELECTED", route: pathname});
|
||||||
|
setSelectedKey(pathname);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectedKey && document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
||||||
|
behavior: "smooth", block: "center"
|
||||||
|
});
|
||||||
|
}, [selectedKey]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List disablePadding>
|
||||||
|
{
|
||||||
|
menuItems.map((menuItem) => {
|
||||||
|
return (
|
||||||
|
<SidebarListItems dispatch={dispatch} key={menuItem.id} menuItem={menuItem}/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</List>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default SidebarMenu
|
||||||
74
src/core/components/SidebarSubitems.jsx
Normal file
74
src/core/components/SidebarSubitems.jsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import {Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText} from "@mui/material";
|
||||||
|
import {ExpandLess, ExpandMore} from "@mui/icons-material";
|
||||||
|
|
||||||
|
const SidebarSubitems = ({subitem, dispatch}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<List disablePadding>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton sx={{
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
},
|
||||||
|
}} selected={subitem.selected} onClick={() => {
|
||||||
|
dispatch({type: "COLLAPSE_SUB_ITEMS", id: subitem.id})
|
||||||
|
}}>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 0,
|
||||||
|
justifyContent: "center",
|
||||||
|
color: "primary.main",
|
||||||
|
width: 40,
|
||||||
|
height: 24,
|
||||||
|
pr: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subitem.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={subitem.firstName}/>
|
||||||
|
{subitem.hasSubitems ? (subitem.showSubitems ? <ExpandLess/> : <ExpandMore/>) : null
|
||||||
|
}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
{
|
||||||
|
subitem.hasSubitems ? (subitem.Subitems.map((subSubitem, index) => {
|
||||||
|
return (
|
||||||
|
<Collapse key={index} in={subitem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
|
<List disablePadding>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
dispatch({type: "COLLAPSE_SUB_SECOND_ITEMS", id: subSubitem.id})
|
||||||
|
}}
|
||||||
|
selected={subSubitem.selected}
|
||||||
|
sx={{
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 0,
|
||||||
|
justifyContent: "center",
|
||||||
|
color: "primary.main",
|
||||||
|
width: 40,
|
||||||
|
height: 24,
|
||||||
|
pr: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subSubitem.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={subSubitem.shahrokh}/>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
)
|
||||||
|
})) : null
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default SidebarSubitems
|
||||||
108
src/lib/contexts/tableSetting.jsx
Normal file
108
src/lib/contexts/tableSetting.jsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
'use client'
|
||||||
|
import {createContext, useCallback, useEffect, useState} from "react";
|
||||||
|
|
||||||
|
export const TableSettingContext = createContext();
|
||||||
|
export const TableSettingProvider = ({children}) => {
|
||||||
|
const [settingStore, setSettingStore] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const _localStorage = localStorage.getItem("_setting-app")
|
||||||
|
if (_localStorage) {
|
||||||
|
setSettingStore(JSON.parse(_localStorage))
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hideAction = useCallback((user_id, page_name, table_name, settingValue, columns) => {
|
||||||
|
updateSettingStorage(user_id, page_name, table_name, "hides", settingValue, columns);
|
||||||
|
}, [])
|
||||||
|
const sortAction = useCallback((user_id, page_name, table_name, settingValue, columns) => {
|
||||||
|
updateSettingStorage(user_id, page_name, table_name, "sorts", settingValue, columns);
|
||||||
|
}, [])
|
||||||
|
const filterAction = (user_id, page_name, table_name, settingValue, columns) => {
|
||||||
|
updateSettingStorage(user_id, page_name, table_name, "filters", settingValue, columns);
|
||||||
|
}
|
||||||
|
const updateSettingStorage = (user_id, page_name, table_name, settingType, settingValue, columns) => {
|
||||||
|
const prevLocalStorage = JSON.parse(localStorage.getItem("_setting-app")) || {};
|
||||||
|
const userSettings = prevLocalStorage[user_id] || {};
|
||||||
|
const pageSettings = userSettings[page_name] || {};
|
||||||
|
const tableSettings = pageSettings[table_name] || {};
|
||||||
|
let newSettings;
|
||||||
|
if (settingType === "sorts" || settingType === "filters") {
|
||||||
|
newSettings = [...settingValue];
|
||||||
|
} else {
|
||||||
|
newSettings = {...tableSettings[settingType] || {}, ...settingValue};
|
||||||
|
}
|
||||||
|
const updatedLocalStorage = {
|
||||||
|
...prevLocalStorage,
|
||||||
|
[user_id]: {
|
||||||
|
...userSettings,
|
||||||
|
[page_name]: {
|
||||||
|
...pageSettings,
|
||||||
|
[table_name]: {
|
||||||
|
...tableSettings,
|
||||||
|
[settingType]: Array.isArray(settingValue) ? [...newSettings] : {...newSettings},
|
||||||
|
summary: SummaryTextMessage({
|
||||||
|
...tableSettings,
|
||||||
|
[settingType]: Array.isArray(settingValue) ? [...newSettings] : {...newSettings},
|
||||||
|
}, columns)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
localStorage.setItem("_setting-app", JSON.stringify(updatedLocalStorage));
|
||||||
|
setSettingStore(updatedLocalStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SummaryTextMessage = (values, columns) => {
|
||||||
|
let SummaryText = "";
|
||||||
|
let HideText = "";
|
||||||
|
let FilterText;
|
||||||
|
let SortText = "";
|
||||||
|
|
||||||
|
if (values.sorts && values.sorts.length > 0) {
|
||||||
|
const _list = []
|
||||||
|
for (const sort of values.sorts) {
|
||||||
|
const _column = columns.find(column => column.id == sort.id)
|
||||||
|
_list.push(`${_column.header}` + `(${sort.desc ? "نزولی" : "صعودی"})`)
|
||||||
|
}
|
||||||
|
if (_list.length > 0)
|
||||||
|
SortText = " مرتب شده بر اساس " + _list.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.hides) {
|
||||||
|
const hidesArray = Object.entries(values.hides).map((e) => ({id: e[0], value: e[1]}))
|
||||||
|
if (hidesArray.length > 0) {
|
||||||
|
const _list = []
|
||||||
|
for (const hide of hidesArray) {
|
||||||
|
if (hide.value) continue
|
||||||
|
const _column = columns.find(c => c.id == hide.id)
|
||||||
|
_list.push(_column.header)
|
||||||
|
}
|
||||||
|
if (_list.length > 0)
|
||||||
|
HideText = "ستون های مخفی شده : " + _list.join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HideText || SortText) {
|
||||||
|
if (SortText) {
|
||||||
|
SummaryText += SortText;
|
||||||
|
if (HideText) {
|
||||||
|
SummaryText += " ; ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (HideText) {
|
||||||
|
SummaryText += HideText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SummaryText;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableSettingContext.Provider
|
||||||
|
value={{settingStore, hideAction, sortAction, filterAction}}>
|
||||||
|
{children}
|
||||||
|
</TableSettingContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
9
src/lib/hooks/useTableSetting.jsx
Normal file
9
src/lib/hooks/useTableSetting.jsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import {useContext} from "react";
|
||||||
|
import {TableSettingContext} from "../contexts/tableSetting";
|
||||||
|
|
||||||
|
const useUser = () => {
|
||||||
|
const {settingStore, hideAction, sortAction, filterAction} = useContext(TableSettingContext);
|
||||||
|
return {settingStore, hideAction, sortAction, filterAction};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useUser;
|
||||||
Reference in New Issue
Block a user