Skip to content

Commit e30243a

Browse files
committed
[FEATURE] Table: Add fitler to the table
Signed-off-by: Mahmoud Shahrokni <seyedmahmoud.shahrokni@amadeus.com> Signed-off-by: Mahmoud Shahrokni <seyedmahmoud.shahrokni@amadeus.com> Signed-off-by: Mahmoud Shahrokni <seyedmahmoud.shahrokni@amadeus.com>
1 parent 9ebdd4f commit e30243a

4 files changed

Lines changed: 361 additions & 19 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright The Perses Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
import { ReactElement } from 'react';
15+
import { Box, Checkbox, Divider, FormControlLabel, Theme, Typography, ClickAwayListener } from '@mui/material';
16+
17+
interface Props {
18+
allValues: Array<string | number>;
19+
selectedValues: Array<string | number>;
20+
onFilterChange: (values: Array<string | number>) => void;
21+
handleFilterClose: () => void;
22+
theme: Theme;
23+
width?: number | 'auto';
24+
}
25+
26+
export const ColumnFilterDropdown = ({
27+
allValues,
28+
selectedValues,
29+
onFilterChange,
30+
handleFilterClose,
31+
theme,
32+
width,
33+
}: Props): ReactElement => {
34+
const values = [...new Set(allValues)].filter((v) => v !== null).sort();
35+
if (!values.length) {
36+
return (
37+
<ClickAwayListener onClickAway={handleFilterClose}>
38+
<Box
39+
sx={{
40+
position: 'absolute',
41+
top: '100%',
42+
left: 0,
43+
zIndex: 1000,
44+
marginTop: '4px',
45+
}}
46+
>
47+
<Box
48+
data-filter-dropdown
49+
sx={{
50+
width: `${width}px`,
51+
minWidth: '200px',
52+
padding: 10,
53+
backgroundColor: theme.palette.background.paper,
54+
border: `1px solid ${theme.palette.divider}`,
55+
borderRadius: 4,
56+
boxShadow: theme.shadows[4],
57+
}}
58+
>
59+
<Typography sx={{ color: theme.palette.text.secondary, fontSize: 14 }}>No values found</Typography>
60+
</Box>
61+
</Box>
62+
</ClickAwayListener>
63+
);
64+
}
65+
66+
return (
67+
<ClickAwayListener onClickAway={handleFilterClose}>
68+
<Box
69+
sx={{
70+
position: 'absolute',
71+
top: '100%',
72+
left: 0,
73+
zIndex: 1000,
74+
marginTop: '4px',
75+
}}
76+
>
77+
<Box
78+
data-filter-dropdown
79+
sx={{
80+
width: `${width}px`,
81+
minWidth: '200px',
82+
padding: '10px',
83+
backgroundColor: theme.palette.background.paper,
84+
border: `1px solid ${theme.palette.divider}`,
85+
borderRadius: 4,
86+
boxShadow: theme.shadows[4],
87+
maxHeight: 250,
88+
overflowY: 'auto',
89+
}}
90+
>
91+
<Box style={{ marginBottom: 8, fontSize: 14, fontWeight: 'bold' }}>
92+
<FormControlLabel
93+
control={
94+
<Checkbox
95+
checked={selectedValues.length === values.length && values.length > 0}
96+
onChange={(e) => onFilterChange(e.target.checked ? values : [])}
97+
indeterminate={selectedValues.length > 0 && selectedValues.length < values.length}
98+
/>
99+
}
100+
label={<Typography sx={{ color: 'text.primary' }}>Select All ({values.length})</Typography>}
101+
/>
102+
</Box>
103+
<Divider sx={{ my: 1 }} />
104+
{values.map((value, index) => (
105+
<Box key={`value-${index}`} style={{ marginBottom: 4 }}>
106+
<FormControlLabel
107+
sx={{
108+
display: 'flex',
109+
alignItems: 'center',
110+
padding: '2px 0',
111+
borderRadius: '4px',
112+
cursor: 'pointer',
113+
}}
114+
control={
115+
<Checkbox
116+
size="small"
117+
checked={selectedValues.includes(value)}
118+
onChange={(e) => {
119+
if (e.target.checked) {
120+
onFilterChange([...selectedValues, value]);
121+
} else {
122+
onFilterChange(selectedValues.filter((v) => v !== value));
123+
}
124+
}}
125+
/>
126+
}
127+
label={
128+
<Typography variant="body2" sx={{ color: 'text.primary', fontSize: 14 }}>
129+
{!value && value !== 0 ? '(empty)' : String(value)}
130+
</Typography>
131+
}
132+
/>
133+
</Box>
134+
))}
135+
</Box>
136+
</Box>
137+
</ClickAwayListener>
138+
);
139+
};

components/src/Table/Table.tsx

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import { Stack, useTheme } from '@mui/material';
1515
import {
1616
ColumnDef,
17+
ColumnFiltersState,
1718
OnChangeFn,
1819
Row,
1920
RowSelectionState,
@@ -24,11 +25,14 @@ import {
2425
getSortedRowModel,
2526
useReactTable,
2627
} from '@tanstack/react-table';
27-
import { ReactElement, useCallback, useMemo } from 'react';
28+
import { ReactElement, useCallback, useMemo, useState } from 'react';
29+
2830
import { TableCheckbox } from './TableCheckbox';
2931
import { VirtualizedTable } from './VirtualizedTable';
3032
import { DEFAULT_COLUMN_WIDTH, TableProps, persesColumnsToTanstackColumns } from './model/table-model';
3133

34+
import { TableFilter } from './TableFilter';
35+
3236
const DEFAULT_GET_ROW_ID = (data: unknown, index: number): string => {
3337
return `${index}`;
3438
};
@@ -64,10 +68,11 @@ export function Table<TableData>({
6468
pagination,
6569
onPaginationChange,
6670
rowSelectionVariant = 'standard',
71+
filteringEnabled = false,
72+
width,
6773
...otherProps
6874
}: TableProps<TableData>): ReactElement {
6975
const theme = useTheme();
70-
7176
const handleRowSelectionChange: OnChangeFn<RowSelectionState> = (rowSelectionUpdater) => {
7277
const newRowSelection =
7378
typeof rowSelectionUpdater === 'function' ? rowSelectionUpdater(rowSelection) : rowSelectionUpdater;
@@ -102,8 +107,8 @@ export function Table<TableData>({
102107
e.nativeEvent && (e.nativeEvent instanceof MouseEvent || e.nativeEvent instanceof KeyboardEvent)
103108
? (e.nativeEvent as PointerEvent)
104109
: undefined;
105-
const isModifed = !!nativePointerEvent?.metaKey || !!nativePointerEvent?.shiftKey;
106-
handleRowSelectionEvent(table, row, isModifed);
110+
const isModified = !!nativePointerEvent?.metaKey || !!nativePointerEvent?.shiftKey;
111+
handleRowSelectionEvent(table, row, isModified);
107112
},
108113
[handleRowSelectionEvent]
109114
);
@@ -174,8 +179,27 @@ export function Table<TableData>({
174179
return initTableColumns;
175180
}, [checkboxColumn, checkboxSelection, columns, hasItemActions, actionsColumn]);
176181

182+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
183+
184+
const filteredData = useMemo(() => {
185+
if (!filteringEnabled || !columnFilters.length) {
186+
return [...data];
187+
}
188+
189+
const x = data.filter((row) =>
190+
columnFilters.every(({ id, value }) => {
191+
const rowValue = (row as Record<string, unknown>)[id];
192+
const filterValues = value as Array<string | number>;
193+
// Use optional chaining and early return for clarity
194+
if (!filterValues?.length) return true;
195+
return filterValues.includes(rowValue as string | number);
196+
})
197+
);
198+
return x;
199+
}, [data, filteringEnabled, columnFilters]);
200+
177201
const table = useReactTable({
178-
data,
202+
data: filteredData,
179203
columns: tableColumns,
180204
getRowId,
181205
getCoreRowModel: getCoreRowModel(),
@@ -207,19 +231,32 @@ export function Table<TableData>({
207231
);
208232

209233
return (
210-
<VirtualizedTable
211-
{...otherProps}
212-
density={density}
213-
defaultColumnWidth={defaultColumnWidth}
214-
defaultColumnHeight={defaultColumnHeight}
215-
onRowClick={handleRowClick}
216-
rows={table.getRowModel().rows}
217-
columns={table.getAllFlatColumns()}
218-
headers={table.getHeaderGroups()}
219-
cellConfigs={cellConfigs}
220-
pagination={pagination}
221-
onPaginationChange={onPaginationChange}
222-
rowCount={table.getRowCount()}
223-
/>
234+
<>
235+
{filteringEnabled && (
236+
<TableFilter
237+
columnFilters={columnFilters}
238+
setColumnFilters={setColumnFilters}
239+
columns={columns}
240+
width={width}
241+
defaultColumnWidth={defaultColumnWidth}
242+
data={data}
243+
/>
244+
)}
245+
<VirtualizedTable
246+
{...otherProps}
247+
width={width}
248+
density={density}
249+
defaultColumnWidth={defaultColumnWidth}
250+
defaultColumnHeight={defaultColumnHeight}
251+
onRowClick={handleRowClick}
252+
rows={table.getRowModel().rows}
253+
columns={table.getAllFlatColumns()}
254+
headers={table.getHeaderGroups()}
255+
cellConfigs={cellConfigs}
256+
pagination={pagination}
257+
onPaginationChange={onPaginationChange}
258+
rowCount={table.getRowCount()}
259+
/>
260+
</>
224261
);
225262
}

0 commit comments

Comments
 (0)