From 8f93feb60917cf35ec2a98020ab82da5318f6256 Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Wed, 13 Mar 2024 16:07:55 +0330 Subject: [PATCH] filter --- package.json | 5 + src/components/BetweenDateFilter.jsx | 31 ++++ src/components/BetweenNumberFilter.jsx | 58 +++++++ src/components/CustomToolbar.jsx | 20 +++ src/components/DataTableMain.jsx | 49 +++++- src/components/DataTableTest.jsx | 13 +- src/components/FilterBox.jsx | 30 ++++ src/components/FilterColumn.jsx | 218 +++++++++++++++++++------ src/components/MuiDatePicker.jsx | 63 +++++++ src/components/SelectBox.jsx | 53 ++++++ src/components/SelectBoxMultiple.jsx | 113 +++++++++++++ src/components/TextFieldWithFnBox.jsx | 21 ++- src/lib/contexts/tableSetting.jsx | 6 +- 13 files changed, 616 insertions(+), 64 deletions(-) create mode 100644 src/components/BetweenDateFilter.jsx create mode 100644 src/components/BetweenNumberFilter.jsx create mode 100644 src/components/CustomToolbar.jsx create mode 100644 src/components/FilterBox.jsx create mode 100644 src/components/MuiDatePicker.jsx create mode 100644 src/components/SelectBox.jsx create mode 100644 src/components/SelectBoxMultiple.jsx diff --git a/package.json b/package.json index a74c3db..3855313 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,12 @@ "@mui/icons-material": "^5.15.6", "@mui/material": "^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", "react": "^18", diff --git a/src/components/BetweenDateFilter.jsx b/src/components/BetweenDateFilter.jsx new file mode 100644 index 0000000..e8e8c18 --- /dev/null +++ b/src/components/BetweenDateFilter.jsx @@ -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 ( + + + handleDateChange(0, value)}/> + + + handleDateChange(1, value)}/> + + + ); +}; + +export default BetWeenDateFilter; diff --git a/src/components/BetweenNumberFilter.jsx b/src/components/BetweenNumberFilter.jsx new file mode 100644 index 0000000..0b3512a --- /dev/null +++ b/src/components/BetweenNumberFilter.jsx @@ -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 ( + + + + + + + + + ); +}; + +export default BetweenNumberFilter; diff --git a/src/components/CustomToolbar.jsx b/src/components/CustomToolbar.jsx new file mode 100644 index 0000000..344b5da --- /dev/null +++ b/src/components/CustomToolbar.jsx @@ -0,0 +1,20 @@ +import FilterColumn from "@/components/FilterColumn"; + +const CustomToolbar = ({columns}) => { + return ( + + ) +} +export default CustomToolbar + + + + + + + + + + + + diff --git a/src/components/DataTableMain.jsx b/src/components/DataTableMain.jsx index 76f4b28..a6e8c91 100644 --- a/src/components/DataTableMain.jsx +++ b/src/components/DataTableMain.jsx @@ -1,9 +1,9 @@ import DataTableTest from "@/components/DataTableTest"; import FilterColumn from "@/components/FilterColumn"; import {useMemo} from "react"; +import {Button} from "@mui/material"; function DataTableMain() { - const columns = useMemo( () => [ { @@ -81,13 +81,58 @@ function DataTableMain() { "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 ( <> - ); diff --git a/src/components/DataTableTest.jsx b/src/components/DataTableTest.jsx index 7b988ae..bf610fb 100644 --- a/src/components/DataTableTest.jsx +++ b/src/components/DataTableTest.jsx @@ -2,7 +2,8 @@ import {useMemo, useState} from 'react'; import {MaterialReactTable} from 'material-react-table'; import useTableSetting from "@/lib/hooks/useTableSetting"; -import {Typography} from "@mui/material"; +import {IconButton, Tooltip, Typography} from "@mui/material"; +import CustomToolbar from "@/components/CustomToolbar"; const data = [ @@ -53,13 +54,16 @@ const data = [ }, ]; +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); @@ -76,6 +80,11 @@ const TestDataTable = ({columns}) => { columns={columns} data={data} manualSorting={true} + renderTopToolbarCustomActions={({table}) => (<> + + )} initialState={{ columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'], sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || [] diff --git a/src/components/FilterBox.jsx b/src/components/FilterBox.jsx new file mode 100644 index 0000000..f887918 --- /dev/null +++ b/src/components/FilterBox.jsx @@ -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 ( +
+ + + + + {columnFilterModeOptions.map((option, index) => ( + handleSelectFilter(option)} selected={option === defaultFilter} sx={{ cursor: 'pointer' }}> + + + ))} + + + + +
+ ); +} + +export default FilterBox; diff --git a/src/components/FilterColumn.jsx b/src/components/FilterColumn.jsx index 28cbf0a..46e156b 100644 --- a/src/components/FilterColumn.jsx +++ b/src/components/FilterColumn.jsx @@ -1,83 +1,203 @@ "use client" import { - Button, - Dialog, DialogActions, - DialogContent, - DialogTitle, - FormControl + 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({ onSubmit , column }) { +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 : []); - console.log(selectedFilters) - }, [settingStore]); const handleSelectFilter = (fieldName, filterFn) => { - const updatedFilters = [...selectedFilters]; - const filterIndex = updatedFilters.findIndex(filter => filter.id === fieldName); + const filterIndex = selectedFilters.findIndex(filter => filter.id === fieldName); + if (filterIndex !== -1) { + const updatedFilters = [...selectedFilters]; updatedFilters[filterIndex].fn = filterFn; + setSelectedFilters(updatedFilters); } else { - updatedFilters.push({ id: fieldName, fn: filterFn }); + const newFilter = { id: fieldName, fn: filterFn }; + const updatedFilters = [...selectedFilters, newFilter]; + setSelectedFilters(updatedFilters); } - setSelectedFilters(updatedFilters); }; - const formik = useFormik({ - initialValues: column.reduce((acc, column) => { - if (column.enableColumnFilter) { - acc[column.field] = ''; + 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; - }, {}), - onSubmit: (values) => { - // const selectedFilters = [ - // { "id": "name", "value": values.name1, "fn": values.cityFn, "datatype": values.nameDatatype }, - // { "id": "navgan_id", "value": values.name2, "fn": values.nameFn, "datatype": values.nameDatatype } - // ]; - - } + 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 ( - - Select Filters - - - {column.map((column, index) => ( - column.enableColumnFilter && - handleSelectFilter(column.field, filterFn)} - /> - ))} - - - - - - + <> + + + + فیلتر + + + + + + + {columns.map((column, index) => ( + column.enableColumnFilter && + (column.filterFn === 'betweenDate' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''} + /> + : column.filterFn === 'betweenNum' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ["", ""]} + /> + : column.type === 'select2' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || []} + /> + : column.type === 'select' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''} + /> + : + 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 || ''} + /> + ) + ))} + + + + + + + + + + ); } diff --git a/src/components/MuiDatePicker.jsx b/src/components/MuiDatePicker.jsx new file mode 100644 index 0000000..0929c61 --- /dev/null +++ b/src/components/MuiDatePicker.jsx @@ -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 ( + + + + { + 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} + /> + + + { + formik.setFieldValue(name, ""); + setPickerValue(null); + }} + sx={{ + color: "#bfbfbf", + position: 'absolute', + right: '4px', + top: '35%', + transform: 'translateY(-50%)', + }} + > + + + + ); +} diff --git a/src/components/SelectBox.jsx b/src/components/SelectBox.jsx new file mode 100644 index 0000000..88cce6e --- /dev/null +++ b/src/components/SelectBox.jsx @@ -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 ( + <> + + انتخاب + + + + ); +}; + +export default SelectBox; diff --git a/src/components/SelectBoxMultiple.jsx b/src/components/SelectBoxMultiple.jsx new file mode 100644 index 0000000..b3b943d --- /dev/null +++ b/src/components/SelectBoxMultiple.jsx @@ -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 ( + <> + + انتخاب + + + + ); +}; + +export default SelectBoxMultiple; diff --git a/src/components/TextFieldWithFnBox.jsx b/src/components/TextFieldWithFnBox.jsx index a3c277d..6ba443b 100644 --- a/src/components/TextFieldWithFnBox.jsx +++ b/src/components/TextFieldWithFnBox.jsx @@ -1,12 +1,17 @@ import {IconButton, Paper, TextField} from '@mui/material'; -import AlignHorizontalCenterIcon from '@mui/icons-material/AlignHorizontalCenter'; -import {useState} from "react"; -// import FilterBox from "@/components/FiterBox"; +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 TextFieldWithFnBox = ({formik, id , 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); @@ -21,16 +26,16 @@ const TextFieldWithFnBox = ({formik, id , defaultFilter , columnFilterModeOptio <> - + ) }} @@ -38,7 +43,7 @@ const TextFieldWithFnBox = ({formik, id , defaultFilter , columnFilterModeOptio position: 'relative' }} /> - {/**/} + ); }; diff --git a/src/lib/contexts/tableSetting.jsx b/src/lib/contexts/tableSetting.jsx index 8ade22b..d787d87 100644 --- a/src/lib/contexts/tableSetting.jsx +++ b/src/lib/contexts/tableSetting.jsx @@ -19,8 +19,8 @@ export const TableSettingProvider = ({children}) => { 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) => { - updateSettingStorage(user_id, page_name, table_name, "filters", settingValue); + 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")) || {}; @@ -28,7 +28,7 @@ export const TableSettingProvider = ({children}) => { const pageSettings = userSettings[page_name] || {}; const tableSettings = pageSettings[table_name] || {}; let newSettings; - if (settingType === "sorts") { + if (settingType === "sorts" || settingType === "filters") { newSettings = [...settingValue]; } else { newSettings = {...tableSettings[settingType] || {}, ...settingValue};