99 useTable ,
1010} from "react-table" ;
1111
12+ import { FilterDropdown } from './FilterDropdown' ;
1213import { Pagination } from "./Pagination" ;
1314import type { PaginationProps } from "./Pagination" ;
1415import { TableHeader } from "./TableHeader" ;
@@ -20,6 +21,7 @@ import type { ThemeProps } from "../types/theme";
2021import type {
2122 Column ,
2223 DataItem ,
24+ FilterOption ,
2325 SelectionState ,
2426 TableInstanceWithHooks ,
2527 TableStateWithPagination ,
@@ -36,6 +38,8 @@ import "../styles/MultiLevelTable.css";
3638 * @property {(row: DataItem) => void } [onRowClick] - Optional callback function when a parent row is clicked
3739 * @property {string[] } [searchableColumns] - Array of column keys to search in
3840 * @property {boolean } [showSearchBar=true] - Whether to show the search bar
41+ * @property {string } [filterColumn] - The column to filter by
42+ * @property {FilterOption[] } [filterOptions] - Array of filter options
3943 */
4044export interface MultiLevelTableProps {
4145 data : DataItem [ ] ;
@@ -52,6 +56,8 @@ export interface MultiLevelTableProps {
5256 onRowClick ?: ( row : DataItem ) => void ;
5357 searchableColumns ?: string [ ] ;
5458 showSearchBar ?: boolean ;
59+ filterColumn ?: string ;
60+ filterOptions ?: FilterOption [ ] ;
5561}
5662
5763/**
@@ -75,6 +81,8 @@ export const MultiLevelTable: React.FC<MultiLevelTableProps> = ({
7581 onRowClick,
7682 searchableColumns,
7783 showSearchBar = true ,
84+ filterColumn,
85+ filterOptions = [ ] ,
7886} ) => {
7987 const mergedTheme = mergeThemeProps ( defaultTheme , theme ) ;
8088 const [ selectionState , setSelectionState ] = useState < SelectionState > ( {
@@ -85,30 +93,47 @@ export const MultiLevelTable: React.FC<MultiLevelTableProps> = ({
8593 // Search state
8694 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
8795
96+ const [ showFilterDropdown , setShowFilterDropdown ] = useState ( false ) ;
97+
98+ const [ selectedFilterValues , setSelectedFilterValues ] = useState < Set < string | number > > ( new Set ( ) ) ;
99+
88100 // Use provided searchableColumns or all columns
89101 const searchCols = useMemo ( ( ) => {
90- if ( searchableColumns && searchableColumns . length > 0 ) return searchableColumns ;
102+ if ( searchableColumns && searchableColumns . length > 0 )
103+ return searchableColumns ;
91104
92105 return columns . map ( col => col . key ) ;
93106 } , [ searchableColumns , columns ] ) ;
94107
95- // Filtered data based on search
108+ // Filtered data based on search and filter
96109 const filteredData = useMemo ( ( ) => {
97- if ( ! searchTerm )
110+ let filtered = data ;
98111
99- return data ;
112+ // Apply search filter
113+ if ( searchTerm ) {
114+ const lowerSearch = searchTerm . toLowerCase ( ) ;
100115
101- const lowerSearch = searchTerm . toLowerCase ( ) ;
116+ filtered = filtered . filter ( row =>
117+ searchCols . some ( colKey => {
118+ const value = row [ colKey as keyof DataItem ] ;
102119
103- return data . filter ( row =>
104- searchCols . some ( colKey => {
105- const value = row [ colKey as keyof DataItem ] ;
120+ return value && value . toString ( ) . toLowerCase ( ) . includes ( lowerSearch ) ;
121+ } )
122+ ) ;
123+ }
106124
107- return value && value . toString ( ) . toLowerCase ( ) . includes ( lowerSearch ) ;
108- } )
109- ) ;
125+ // Apply column filter
126+ if ( filterColumn && selectedFilterValues . size > 0 )
127+ filtered = filtered . filter ( row => {
128+ const value = row [ filterColumn as keyof DataItem ] ;
110129
111- } , [ data , searchTerm , searchCols ] ) ;
130+ return typeof value === 'string' || typeof value === 'number'
131+ ? selectedFilterValues . has ( value )
132+ : false ;
133+ } ) ;
134+
135+ return filtered ;
136+ } , [ data , searchTerm , searchCols , filterColumn , selectedFilterValues ] ) ;
112137
113138 // Get all parent row IDs (level 0)
114139 const parentRowIds = useMemo ( ( ) => data . map ( item => item . id ) , [ data ] ) ;
@@ -338,6 +363,56 @@ export const MultiLevelTable: React.FC<MultiLevelTableProps> = ({
338363 ) ;
339364 } ;
340365
366+ const handleFilterChange = ( values : Set < string | number > ) => {
367+ setSelectedFilterValues ( values ) ;
368+ } ;
369+
370+ const renderFilterButton = ( ) => {
371+ if ( ! filterColumn || ! filterOptions . length ) return null ;
372+
373+ return (
374+ < div style = { { position : 'relative' } } >
375+ < button
376+ style = { {
377+ padding : "0.5rem 1rem" ,
378+ borderRadius : "4px" ,
379+ border : `1px solid ${ mergedTheme . table ?. cell ?. borderColor || "#e2e8f0" } ` ,
380+ backgroundColor : mergedTheme . colors ?. background || "#ffffff" ,
381+ color : mergedTheme . colors ?. textColor || "#000000" ,
382+ cursor : "pointer" ,
383+ fontSize : "0.875rem" ,
384+ display : "flex" ,
385+ alignItems : "center" ,
386+ gap : "0.5rem" ,
387+ } }
388+ onClick = { ( ) => setShowFilterDropdown ( ! showFilterDropdown ) }
389+ >
390+ < span > Filter</ span >
391+ { selectedFilterValues . size > 0 && (
392+ < span style = { {
393+ backgroundColor : '#5d5fef' ,
394+ color : '#ffffff' ,
395+ borderRadius : '12px' ,
396+ padding : '2px 8px' ,
397+ fontSize : '0.75rem' ,
398+ } } >
399+ { selectedFilterValues . size }
400+ </ span >
401+ ) }
402+ </ button >
403+ { showFilterDropdown && (
404+ < FilterDropdown
405+ options = { filterOptions }
406+ selectedValues = { selectedFilterValues }
407+ onFilterChange = { handleFilterChange }
408+ onClose = { ( ) => setShowFilterDropdown ( false ) }
409+ theme = { mergedTheme }
410+ />
411+ ) }
412+ </ div >
413+ ) ;
414+ } ;
415+
341416 return (
342417 < div style = { { backgroundColor : mergedTheme . colors ?. background } } >
343418 < div className = "table-wrapper" >
@@ -412,22 +487,7 @@ export const MultiLevelTable: React.FC<MultiLevelTableProps> = ({
412487 >
413488 < span > Export</ span >
414489 </ button >
415- < button
416- style = { {
417- padding : "0.5rem 1rem" ,
418- borderRadius : "4px" ,
419- border : `1px solid ${ mergedTheme . table ?. cell ?. borderColor || "#e2e8f0" } ` ,
420- backgroundColor : mergedTheme . colors ?. background || "#ffffff" ,
421- color : mergedTheme . colors ?. textColor || "#000000" ,
422- cursor : "pointer" ,
423- fontSize : "0.875rem" ,
424- display : "flex" ,
425- alignItems : "center" ,
426- gap : "0.5rem" ,
427- } }
428- >
429- < span > Filter</ span >
430- </ button >
490+ { renderFilterButton ( ) }
431491 </ div >
432492 </ div >
433493 ) }
0 commit comments