Add support for negative filters
Summary: Changelog: Add support for negative filters in data tables As requested per somewhere on workplace but couldn't find it back :) Reviewed By: nikoant Differential Revision: D29486096 fbshipit-source-id: 467c8598f6d09afc9a5ed85affb6c51840afe00c
This commit is contained in:
committed by
Facebook GitHub Bot
parent
8e0d3cf779
commit
6c7b69803f
@@ -10,7 +10,15 @@
|
||||
import {useMemo, useState} from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import React from 'react';
|
||||
import {Button, Checkbox, Dropdown, Menu, Typography, Input} from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Typography,
|
||||
Input,
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import {
|
||||
FilterOutlined,
|
||||
MinusCircleOutlined,
|
||||
@@ -115,6 +123,29 @@ export function FilterIcon({
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
<Menu.Item>
|
||||
<Layout.Horizontal
|
||||
gap
|
||||
center
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}>
|
||||
<Switch
|
||||
checked={!!column.inversed}
|
||||
size="small"
|
||||
onChange={(inversed) => {
|
||||
dispatch({
|
||||
type: 'setColumnFilterInverse',
|
||||
column: column.key,
|
||||
inversed,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
Exclude items matching filter
|
||||
</Layout.Horizontal>
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item disabled>
|
||||
<div style={{textAlign: 'right'}}>
|
||||
<Button
|
||||
|
||||
@@ -106,6 +106,7 @@ export type DataTableColumn<T = any> = {
|
||||
enabled: boolean;
|
||||
predefined?: boolean;
|
||||
}[];
|
||||
inversed?: boolean;
|
||||
};
|
||||
|
||||
export interface TableRowRenderContext<T = any> {
|
||||
@@ -341,7 +342,7 @@ export function DataTable<T extends object>(
|
||||
// Important dep optimization: we don't want to recalc filters if just the width or visibility changes!
|
||||
// We pass entire state.columns to computeDataTableFilter, but only changes in the filter are a valid cause to compute a new filter function
|
||||
// eslint-disable-next-line
|
||||
[tableState.searchValue, tableState.useRegex, ...tableState.columns.map((c) => c.filters)],
|
||||
[tableState.searchValue, tableState.useRegex, ...tableState.columns.map((c) => c.filters), ...tableState.columns.map((c => c.inversed))],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
@@ -599,7 +600,7 @@ function EmptyTable({dataSource}: {dataSource: DataSource<any>}) {
|
||||
<Layout.Container
|
||||
center
|
||||
style={{width: '100%', padding: 40, color: theme.textColorSecondary}}>
|
||||
{dataSource.records.length === 0 ? (
|
||||
{dataSource.size === 0 ? (
|
||||
<>
|
||||
<CoffeeOutlined style={{fontSize: '2em', margin: 8}} />
|
||||
<Typography.Text type="secondary">No records yet</Typography.Text>
|
||||
|
||||
@@ -85,6 +85,7 @@ type DataManagerActions<T> =
|
||||
>
|
||||
| Action<'removeColumnFilter', {column: keyof T; index: number}>
|
||||
| Action<'toggleColumnFilter', {column: keyof T; index: number}>
|
||||
| Action<'setColumnFilterInverse', {column: keyof T; inversed: boolean}>
|
||||
| Action<'setColumnFilterFromSelection', {column: keyof T}>
|
||||
| Action<'appliedInitialScroll'>
|
||||
| Action<'toggleUseRegex'>
|
||||
@@ -218,6 +219,11 @@ export const dataTableManagerReducer = produce<
|
||||
f.enabled = !f.enabled;
|
||||
break;
|
||||
}
|
||||
case 'setColumnFilterInverse': {
|
||||
draft.columns.find((c) => c.key === action.column)!.inversed =
|
||||
action.inversed;
|
||||
break;
|
||||
}
|
||||
case 'setColumnFilterFromSelection': {
|
||||
const items = getSelectedItems(
|
||||
config.dataSource as DataSource<any>,
|
||||
@@ -504,14 +510,15 @@ export function computeDataTableFilter(
|
||||
|
||||
return function dataTableFilter(item: any) {
|
||||
for (const column of filteringColumns) {
|
||||
if (
|
||||
!column.filters!.some(
|
||||
const rowMatchesFilter = column.filters!.some(
|
||||
(f) =>
|
||||
f.enabled &&
|
||||
String(item[column.key]).toLowerCase().includes(f.value),
|
||||
)
|
||||
) {
|
||||
return false; // there are filters, but none matches
|
||||
f.enabled && String(item[column.key]).toLowerCase().includes(f.value),
|
||||
);
|
||||
if (column.inversed && rowMatchesFilter) {
|
||||
return false;
|
||||
}
|
||||
if (!column.inversed && !rowMatchesFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Object.values(item).some((v) =>
|
||||
|
||||
@@ -493,6 +493,40 @@ test('compute filters', () => {
|
||||
])!;
|
||||
expect(data.filter(filter)).toEqual([espresso]);
|
||||
}
|
||||
{
|
||||
// inverse filter
|
||||
const filter = computeDataTableFilter('', false, [
|
||||
{
|
||||
key: 'level',
|
||||
filters: [
|
||||
{
|
||||
enabled: true,
|
||||
value: 'error',
|
||||
label: 'error',
|
||||
},
|
||||
],
|
||||
inversed: true,
|
||||
},
|
||||
])!;
|
||||
expect(data.filter(filter)).toEqual([coffee, espresso]);
|
||||
}
|
||||
{
|
||||
// inverse filter with search
|
||||
const filter = computeDataTableFilter('coffee', false, [
|
||||
{
|
||||
key: 'level',
|
||||
filters: [
|
||||
{
|
||||
enabled: true,
|
||||
value: 'error',
|
||||
label: 'error',
|
||||
},
|
||||
],
|
||||
inversed: true,
|
||||
},
|
||||
])!;
|
||||
expect(data.filter(filter)).toEqual([coffee]);
|
||||
}
|
||||
{
|
||||
const filter = computeDataTableFilter('nonsense', false, [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user