diff --git a/docs/data/data-grid/filtering-recipes/FilteredRowCount.js b/docs/data/data-grid/filtering-recipes/FilteredRowCount.js new file mode 100644 index 000000000000..7f517d35cd22 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteredRowCount.js @@ -0,0 +1,88 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; + +const predefinedFilters = [ + { + label: 'All', + filterModel: { items: [] }, + }, + { + label: 'Filled', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] }, + }, + { + label: 'Open', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] }, + }, + { + label: 'Rejected', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] }, + }, + { + label: 'Partially Filled', + filterModel: { + items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }], + }, + }, +]; + +export default function FilteredRowCount() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 1000, + maxColumns: 10, + }); + + const apiRef = useGridApiRef(); + + const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState( + [], + ); + + const getFilteredRowsCount = React.useCallback( + (filterModel) => { + const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel); + return Object.keys(filteredRowsLookup).filter( + (rowId) => filteredRowsLookup[rowId] === true, + ).length; + }, + [apiRef], + ); + + React.useEffect(() => { + // Calculate the row count for predefined filters + if (data.rows.length === 0) { + return; + } + + setPredefinedFiltersRowCount( + predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)), + ); + }, [apiRef, data.rows, getFilteredRowsCount]); + + return ( +
+ + {predefinedFilters.map(({ label, filterModel }, index) => { + const count = predefinedFiltersRowCount[index]; + return ( + + ); + })} + + + + +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx b/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx new file mode 100644 index 000000000000..df925c75a800 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGridPro, useGridApiRef, GridFilterModel } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; + +const predefinedFilters: { label: string; filterModel: GridFilterModel }[] = [ + { + label: 'All', + filterModel: { items: [] }, + }, + { + label: 'Filled', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] }, + }, + { + label: 'Open', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] }, + }, + { + label: 'Rejected', + filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] }, + }, + { + label: 'Partially Filled', + filterModel: { + items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }], + }, + }, +]; + +export default function FilteredRowCount() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 1000, + maxColumns: 10, + }); + + const apiRef = useGridApiRef(); + + const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState< + number[] + >([]); + + const getFilteredRowsCount = React.useCallback( + (filterModel: GridFilterModel) => { + const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel); + return Object.keys(filteredRowsLookup).filter( + (rowId) => filteredRowsLookup[rowId] === true, + ).length; + }, + [apiRef], + ); + + React.useEffect(() => { + // Calculate the row count for predefined filters + if (data.rows.length === 0) { + return; + } + + setPredefinedFiltersRowCount( + predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)), + ); + }, [apiRef, data.rows, getFilteredRowsCount]); + + return ( +
+ + {predefinedFilters.map(({ label, filterModel }, index) => { + const count = predefinedFiltersRowCount[index]; + return ( + + ); + })} + + + + +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/filtering-recipes.md b/docs/data/data-grid/filtering-recipes/filtering-recipes.md index 6f2f60c5c4d1..fa5322aa7f22 100644 --- a/docs/data/data-grid/filtering-recipes/filtering-recipes.md +++ b/docs/data/data-grid/filtering-recipes/filtering-recipes.md @@ -15,3 +15,9 @@ This requires certain considerations due to the Grid's context structure. The following example shows how to accomplish this: {{"demo": "QuickFilterOutsideOfGrid.js", "bg": "inline", "defaultCodeOpen": false}} + +## Calculating filtered rows in advance + +The [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) provides the [`getFilterState`](/x/api/data-grid/grid-api/#grid-api-prop-getFilterState) method, which allows you to display the row count for predefined filters upfront without applying filters to the Data Grid: + +{{"demo": "FilteredRowCount.js", "bg": "inline", "defaultCodeOpen": false}} diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index a4fc87914cd1..88b2080043cd 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -116,6 +116,12 @@ "required": true, "isProPlan": true }, + "getFilterState": { + "type": { + "description": "(filterModel: GridFilterModel) => GridStateCommunity['filter']" + }, + "required": true + }, "getLocaleText": { "type": { "description": "<T extends GridTranslationKeys>(key: T) => GridLocaleText[T]" diff --git a/docs/pages/x/api/data-grid/grid-filter-api.json b/docs/pages/x/api/data-grid/grid-filter-api.json index 626ae4fcb29e..48c344e8b190 100644 --- a/docs/pages/x/api/data-grid/grid-filter-api.json +++ b/docs/pages/x/api/data-grid/grid-filter-api.json @@ -7,6 +7,11 @@ "description": "Deletes a GridFilterItem.", "type": "(item: GridFilterItem) => void" }, + { + "name": "getFilterState", + "description": "Returns the filter state for the given filter model without applying it to the data grid.", + "type": "(filterModel: GridFilterModel) => GridStateCommunity['filter']" + }, { "name": "hideFilterPanel", "description": "Hides the filter panel.", "type": "() => void" }, { "name": "ignoreDiacritics", diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 02e89d3757ea..cec2f0cfce03 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -66,6 +66,9 @@ "description": "Returns the grid data as an exceljs workbook.
This method is used internally by exportDataAsExcel." }, "getExpandedDetailPanels": { "description": "Returns the rows whose detail panel is open." }, + "getFilterState": { + "description": "Returns the filter state for the given filter model without applying it to the data grid." + }, "getLocaleText": { "description": "Returns the translation for the key." }, "getPinnedColumns": { "description": "Returns which columns are pinned." }, "getRootDimensions": { "description": "Returns the dimensions of the grid" }, diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 27b90dfb2317..031320af23c3 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -107,21 +107,13 @@ export const useGridFilter = ( const updateFilteredRows = React.useCallback(() => { apiRef.current.setState((state) => { const filterModel = gridFilterModelSelector(state, apiRef.current.instanceId); - const isRowMatchingFilters = - props.filterMode === 'client' - ? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval) - : null; - - const filteringResult = apiRef.current.applyStrategyProcessor('filtering', { - isRowMatchingFilters, - filterModel: filterModel ?? getDefaultGridFilterModel(), - }); + const filterState = apiRef.current.getFilterState(filterModel); const newState = { ...state, filter: { ...state.filter, - ...filteringResult, + ...filterState, }, }; @@ -133,7 +125,7 @@ export const useGridFilter = ( }; }); apiRef.current.publishEvent('filteredRowsSet'); - }, [apiRef, props.filterMode, props.disableEval]); + }, [apiRef]); const addColumnMenuItem = React.useCallback>( (columnMenuItems, colDef) => { @@ -321,6 +313,30 @@ export const useGridFilter = ( [apiRef, logger, props.disableMultipleColumnsFiltering], ); + const getFilterState = React.useCallback( + (inputFilterModel) => { + const filterModel = sanitizeFilterModel( + inputFilterModel, + props.disableMultipleColumnsFiltering, + apiRef, + ); + const isRowMatchingFilters = + props.filterMode === 'client' + ? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval) + : null; + + const filterResult = apiRef.current.applyStrategyProcessor('filtering', { + isRowMatchingFilters, + filterModel: filterModel ?? getDefaultGridFilterModel(), + }); + return { + ...filterResult, + filterModel, + }; + }, + [props.disableMultipleColumnsFiltering, props.filterMode, props.disableEval, apiRef], + ); + const filterApi: GridFilterApi = { setFilterLogicOperator, unstable_applyFilters: applyFilters, @@ -332,6 +348,7 @@ export const useGridFilter = ( hideFilterPanel, setQuickFilterValues, ignoreDiacritics: props.ignoreDiacritics, + getFilterState, }; useGridApiMethod(apiRef, filterApi, 'public'); diff --git a/packages/x-data-grid/src/models/api/gridFilterApi.ts b/packages/x-data-grid/src/models/api/gridFilterApi.ts index d1c3ceadadc3..d86aabc67fb2 100644 --- a/packages/x-data-grid/src/models/api/gridFilterApi.ts +++ b/packages/x-data-grid/src/models/api/gridFilterApi.ts @@ -2,6 +2,7 @@ import { GridFilterModel } from '../gridFilterModel'; import { GridFilterItem, GridLogicOperator } from '../gridFilterItem'; import { GridControlledStateReasonLookup } from '../events'; import type { DataGridProcessedProps } from '../props/DataGridProps'; +import { GridStateCommunity } from '../gridStateCommunity'; /** * The filter API interface that is available in the grid [[apiRef]]. @@ -61,4 +62,10 @@ export interface GridFilterApi { * Returns the value of the `ignoreDiacritics` prop. */ ignoreDiacritics: DataGridProcessedProps['ignoreDiacritics']; + /** + * Returns the filter state for the given filter model without applying it to the data grid. + * @param {GridFilterModel} filterModel The filter model to get the state for. + * @returns {GridStateCommunity['filter']} The filter state. + */ + getFilterState: (filterModel: GridFilterModel) => GridStateCommunity['filter']; }