11import { ConfirmDeletionModal } from '@/components/ConfirmDeletionModal' ;
22import { Button } from '@/components/ui/button' ;
3+ import {
4+ DropdownMenu ,
5+ DropdownMenuContent ,
6+ DropdownMenuItem ,
7+ DropdownMenuTrigger ,
8+ } from '@/components/ui/dropdownMenu' ;
39import { useInstanceClientIdParams } from '@/config/useInstanceClient' ;
410import { ColumnFiltersSchema } from '@/features/instance/databases/components/ColumnFilters' ;
511import { PickColumnsDropdown } from '@/features/instance/databases/components/PickColumnsDropdown' ;
@@ -20,19 +26,27 @@ import {
2026} from '@/features/instance/operations/queries/getSearchByConditions' ;
2127import { getSearchByIdOptions } from '@/features/instance/operations/queries/getSearchById' ;
2228import { getSearchByValueOptions } from '@/features/instance/operations/queries/getSearchByValue' ;
23- import { useDebounce } from '@/hooks/useDebounce' ;
2429import { useEffectedState } from '@/hooks/useEffectedState' ;
2530import { useInstanceBrowseManagePermission , useInstanceSchemaTablePermission } from '@/hooks/usePermissions' ;
2631import { useRefreshClick } from '@/hooks/useRefreshClick' ;
2732import { useSessionStorage } from '@/hooks/useSessionStorage' ;
28- import { useToggleCallback } from '@/hooks/useToggleCallback ' ;
33+ import { useToggler } from '@/hooks/useToggler ' ;
2934import { keyBy } from '@/lib/keyBy' ;
3035import { queryClient } from '@/react-query/queryClient' ;
3136import { zodResolver } from '@hookform/resolvers/zod' ;
3237import { useQuery , useSuspenseQuery } from '@tanstack/react-query' ;
3338import { useNavigate , useParams } from '@tanstack/react-router' ;
3439import { Row , VisibilityState } from '@tanstack/react-table' ;
35- import { ImportIcon , PlusIcon , RefreshCwIcon , SearchIcon , Trash } from 'lucide-react' ;
40+ import {
41+ Ellipsis ,
42+ FunnelIcon ,
43+ FunnelPlusIcon ,
44+ FunnelXIcon ,
45+ ImportIcon ,
46+ PlusIcon ,
47+ RefreshCwIcon ,
48+ Trash ,
49+ } from 'lucide-react' ;
3650import { useCallback , useEffect , useMemo , useState } from 'react' ;
3751import { useForm } from 'react-hook-form' ;
3852import { toast } from 'sonner' ;
@@ -61,29 +75,40 @@ export function DatabaseTableView() {
6175 tableName,
6276 } ) ,
6377 ) ;
64- const attributesMap = useMemo ( ( ) => keyBy ( describeTableData . attributes , 'attribute' ) , [ describeTableData ] )
78+ const attributesMap = useMemo ( ( ) => keyBy ( describeTableData . attributes , 'attribute' ) , [ describeTableData ] ) ;
6579 const [ selectedIds , setSelectedIds ] = useEffectedState < null | unknown [ ] > ( null , allParams ) ;
6680 const [ isEditModalOpen , setIsEditModalOpen ] = useState ( false ) ;
6781
82+ const { toggled : filtersToggled , toggleOn : showFilters , toggleOff : hideFilters } = useToggler ( false ) ;
6883 const columnFiltersForm = useForm ( {
6984 resolver : zodResolver ( ColumnFiltersSchema ) ,
7085 } ) ;
86+ const { reset : resetFiltersForm } = columnFiltersForm ;
7187 const columnFiltersValues = columnFiltersForm . watch ( ) ;
72- const debouncedColumnFiltersValues = useDebounce ( columnFiltersValues , 500 , JSON . stringify ) ;
73- const searchConditions : SearchCondition [ ] | null = useMemo ( ( ) => {
88+
89+ const [ appliedSearchConditions , setAppliedSearchConditions ] = useState < SearchCondition [ ] | null > ( null ) ;
90+
91+ const applyFilters = useCallback ( ( ) => {
7492 const conditions : SearchCondition [ ] = [ ] ;
75- for ( const key in debouncedColumnFiltersValues ) {
76- if ( debouncedColumnFiltersValues [ key ] ?. length ) {
93+ for ( const key in columnFiltersValues ) {
94+ if ( columnFiltersValues [ key ] ?. length ) {
7795 try {
78- conditions . push ( ...translateColumnFilterToSearchConditions ( key , debouncedColumnFiltersValues [ key ] , attributesMap [ key ] ) ) ;
79- }
80- catch ( err ) {
96+ conditions . push ( ...translateColumnFilterToSearchConditions ( key , columnFiltersValues [ key ] , attributesMap [ key ] ) ) ;
97+ } catch ( err ) {
8198 toast . error ( String ( err ) ) ;
8299 }
83100 }
84101 }
85- return conditions . length ? conditions : null ;
86- } , [ attributesMap , debouncedColumnFiltersValues ] ) ;
102+ setAppliedSearchConditions ( conditions . length ? conditions : null ) ;
103+ resetFiltersForm ( { ...columnFiltersValues } ) ;
104+ } , [ attributesMap , resetFiltersForm , columnFiltersValues ] ) ;
105+ const clearFilters = useCallback ( ( ) => {
106+ // Note sure why we need to resetFiltersForm twice here...
107+ resetFiltersForm ( { } , { keepValues : false , keepDirtyValues : false , keepDefaultValues : false } ) ;
108+ resetFiltersForm ( ) ;
109+ setAppliedSearchConditions ( null ) ;
110+ hideFilters ( ) ;
111+ } , [ hideFilters , resetFiltersForm ] ) ;
87112
88113 const { dataTableColumns, hashAttribute } = formatBrowseDataTableHeader ( describeTableData ) ;
89114 const [ isAddModalOpen , setIsAddModalOpen ] = useState ( false ) ;
@@ -101,14 +126,16 @@ export function DatabaseTableView() {
101126 const [ pageSize , setPageSize ] = useState ( 20 ) ;
102127 const [ totalPages , setTotalPages ] = useState ( Math . ceil ( describeTableData . record_count / pageSize ) ) ;
103128
129+ const useFilteredList = filtersToggled && ! ! appliedSearchConditions ;
130+
104131 // Full list
105132 const {
106133 data : fullTableData ,
107134 refetch : refetchSearchByValueOptions ,
108135 isFetching : tableDataFetching ,
109136 } = useQuery (
110137 getSearchByValueOptions ( {
111- enabled : ! searchConditions ,
138+ enabled : ! useFilteredList ,
112139 ...instanceParams ,
113140 databaseName,
114141 tableName,
@@ -121,17 +148,17 @@ export function DatabaseTableView() {
121148 // Filtered list
122149 const { data : filteredTableData } = useQuery (
123150 getSearchByConditionsOptions ( {
124- enabled : ! ! searchConditions ,
151+ enabled : useFilteredList ,
125152 ...instanceParams ,
126153 databaseName,
127154 tableName,
128- conditions : searchConditions ,
155+ conditions : appliedSearchConditions ,
129156 sort,
130157 pageSize,
131158 pageIndex,
132159 } ) ,
133160 ) ;
134- const tableData = searchConditions ? filteredTableData : fullTableData ;
161+ const tableData = useFilteredList ? filteredTableData : fullTableData ;
135162 // One by id
136163 const { data : searchByIdData } = useQuery (
137164 getSearchByIdOptions ( {
@@ -280,7 +307,6 @@ export function DatabaseTableView() {
280307 `ColumnDisplayed/${ databaseName } }/${ tableName } ` as 'ColumnDisplayed/{database}/{table}' ,
281308 { } satisfies VisibilityState ,
282309 ) ;
283- const [ showSearch , toggleShowSearch ] = useToggleCallback ( false ) ;
284310
285311 return (
286312 < >
@@ -312,34 +338,63 @@ export function DatabaseTableView() {
312338 </ span >
313339 </ Button >
314340 ) }
315- < Button variant = "defaultOutline" onClick = { onRefreshClick } disabled = { tableDataFetching } >
316- < RefreshCwIcon />
317- </ Button >
318341 </ div >
319342
320343 < div className = "flex space-x-2" >
321- < Button variant = "ghost" onClick = { toggleShowSearch } >
322- < SearchIcon className = "inline-block " />
323- { showSearch ? 'Clear Search' : 'Search' }
344+ { filtersToggled && appliedSearchConditions && (
345+ < Button variant = "ghost" onClick = { clearFilters } accessKey = "f" >
346+ < FunnelXIcon className = "inline-block " />
347+ < span > Clear < u > F</ u > ilters</ span >
348+ </ Button >
349+ ) }
350+ { filtersToggled && columnFiltersForm . formState . isDirty && (
351+ < Button variant = "default" onClick = { applyFilters } >
352+ < FunnelPlusIcon className = "inline-block " />
353+ Apply Filters
354+ </ Button >
355+ ) }
356+ { filtersToggled && ! appliedSearchConditions && (
357+ < Button variant = "ghost" onClick = { hideFilters } accessKey = "f" >
358+ < FunnelXIcon className = "inline-block " />
359+ < span > Hide < u > F</ u > ilters</ span >
360+ </ Button >
361+ ) }
362+
363+ { ! filtersToggled && (
364+ < Button variant = "ghost" onClick = { showFilters } accessKey = "f" >
365+ < FunnelIcon className = "inline-block " />
366+ < span > Show < u > F</ u > ilters</ span >
367+ </ Button >
368+ ) }
369+
370+ < Button variant = "defaultOutline" onClick = { onRefreshClick } disabled = { tableDataFetching } >
371+ < RefreshCwIcon />
324372 </ Button >
373+
325374 < PickColumnsDropdown
326375 columns = { dataTableColumns }
327376 columnVisibility = { columnVisibility }
328377 setColumnVisibility = { setColumnVisibility }
329378 />
330- { canManageBrowseInstance && (
331- < Button variant = "destructiveOutline" onClick = { openDeleteModal } >
332- < Trash className = "inline-block " />
333- Drop Table
334- </ Button >
335- ) }
379+
380+ { canManageBrowseInstance && ( < DropdownMenu >
381+ < DropdownMenuTrigger >
382+ < Ellipsis aria-label = "Table options" />
383+ </ DropdownMenuTrigger >
384+ < DropdownMenuContent side = "bottom" align = "end" >
385+ < DropdownMenuItem className = "focus:bg-red/70 focus:text-white" onClick = { openDeleteModal } >
386+ < Trash className = "inline-block " />
387+ Drop Table
388+ </ DropdownMenuItem >
389+ </ DropdownMenuContent >
390+ </ DropdownMenu > ) }
336391 </ div >
337392 </ div >
338393
339394 < TableView < Record < string , unknown > , unknown >
340395 data = { tableData ?. data || [ ] }
341396 isFetching = { tableDataFetching }
342- showSearch = { showSearch }
397+ filtersToggled = { filtersToggled }
343398 columns = { dataTableColumns }
344399 columnVisibility = { columnVisibility }
345400 onRowClick = { onRowClick }
@@ -349,6 +404,7 @@ export function DatabaseTableView() {
349404 pageIndex = { pageIndex }
350405 pageSize = { pageSize }
351406 columnFiltersForm = { columnFiltersForm }
407+ applyFilters = { applyFilters }
352408 setPageIndex = { setPageIndex }
353409 setPageSize = { setPageSize }
354410 />
0 commit comments