1- import { useCallback , useEffect , useMemo , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
22import { useSearchParams } from 'react-router-dom' ;
33import { useTranslation } from 'react-i18next' ;
44import {
@@ -25,6 +25,8 @@ import { ISortBy, Tbody, Td, Tr } from '@patternfly/react-table';
2525import { ListResponse , Resource } from '@/api/types' ;
2626import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters' ;
2727import { ResourceListParams } from '@/api/hooks/useResourceList' ;
28+ import { UseQueryResult } from '@tanstack/react-query' ;
29+ import { ApiError } from '@/api/client' ;
2830
2931const perPageOptions = [
3032 { title : '5' , value : 5 } ,
@@ -36,6 +38,44 @@ const perPageOptions = [
3638
3739const DEFAULT_PAGE_SIZE = 10 ;
3840
41+ // Custom hook for text filter handlers
42+ const useTextFilterHandlers = (
43+ filterId : string ,
44+ pendingFilters : Record < string , string > ,
45+ setPendingFilters : React . Dispatch < React . SetStateAction < Record < string , string > > > ,
46+ onSetFilters : ( newFilters : Partial < Record < string , string | string [ ] > > ) => void ,
47+ ) => {
48+ const handleChange = useCallback ( ( _event : React . FormEvent < HTMLInputElement > | undefined , value : string ) => {
49+ if ( value ) {
50+ setPendingFilters ( prev => ( { ...prev , [ filterId ] : value } ) ) ;
51+ } else {
52+ onSetFilters ( { [ filterId ] : '' } ) ;
53+ setPendingFilters ( prev => {
54+ const { [ filterId ] : _ , ...rest } = prev ;
55+ return rest ;
56+ } ) ;
57+ }
58+ } , [ filterId ] ) ;
59+
60+ const handleClear = useCallback ( ( ) => {
61+ onSetFilters ( { [ filterId ] : '' } ) ;
62+ setPendingFilters ( prev => {
63+ const { [ filterId ] : _ , ...rest } = prev ;
64+ return rest ;
65+ } ) ;
66+ } , [ filterId ] ) ;
67+
68+ const handleSearch = useCallback ( ( _event : React . SyntheticEvent < HTMLButtonElement > , value : string ) => {
69+ onSetFilters ( { [ filterId ] : pendingFilters [ filterId ] ?? value } ) ;
70+ setPendingFilters ( prev => {
71+ const { [ filterId ] : _ , ...rest } = prev ;
72+ return rest ;
73+ } ) ;
74+ } , [ filterId , pendingFilters [ filterId ] ] ) ;
75+
76+ return { handleChange, handleClear, handleSearch } ;
77+ } ;
78+
3979export interface ResourceListDataViewColumnMapper {
4080 (
4181 sortBy ?: string ,
@@ -53,8 +93,7 @@ export interface ResourceListDataViewRowMapper<T extends Resource> {
5393}
5494
5595export interface ResourceListDataViewProps < T extends Resource > {
56- listResponse : ListResponse < T > | undefined ;
57- isLoading : boolean ;
96+ resourceResult : UseQueryResult < ListResponse < T > , Error > ;
5897 columnProvider : {
5998 dependencies : unknown [ ] ;
6099 callback : ResourceListDataViewColumnMapper ;
@@ -75,8 +114,7 @@ export interface ResourceListDataViewProps<T extends Resource> {
75114}
76115
77116export function ResourceListDataView < T extends Resource > ( {
78- listResponse,
79- isLoading = false ,
117+ resourceResult,
80118 columnProvider,
81119 rowProvider,
82120 dataFilters,
@@ -158,6 +196,7 @@ export function ResourceListDataView<T extends Resource>({
158196 searchParams : sortSearchParams ,
159197 } ) ;
160198
199+ const listResponse = resourceResult . data ;
161200 const totalCount = listResponse ?. meta ?. page ?. total ?? 0 ;
162201
163202 // Track current page number locally to avoid flashing during navigation
@@ -237,42 +276,6 @@ export function ResourceListDataView<T extends Resource>({
237276 onSetFilters ( newValues as Record < string , string | string [ ] > ) ;
238277 } , [ onSetFilters ] ) ;
239278
240- // Custom hook for text filter handlers
241- const useTextFilterHandlers = ( filterId : string ) => {
242- const handleChange = useCallback ( ( _event : React . FormEvent < HTMLInputElement > | undefined , value : string ) => {
243- if ( value ) {
244- setPendingFilters ( prev => ( { ...prev , [ filterId ] : value } ) ) ;
245- } else {
246- onSetFilters ( { [ filterId ] : '' } ) ;
247- setPendingFilters ( prev => {
248- // eslint-disable-next-line @typescript-eslint/no-unused-vars
249- const { [ filterId ] : _ , ...rest } = prev ;
250- return rest ;
251- } ) ;
252- }
253- } , [ filterId ] ) ;
254-
255- const handleClear = useCallback ( ( ) => {
256- onSetFilters ( { [ filterId ] : '' } ) ;
257- setPendingFilters ( prev => {
258- // eslint-disable-next-line @typescript-eslint/no-unused-vars
259- const { [ filterId ] : _ , ...rest } = prev ;
260- return rest ;
261- } ) ;
262- } , [ filterId ] ) ;
263-
264- const handleSearch = useCallback ( ( _event : React . SyntheticEvent < HTMLButtonElement > , value : string ) => {
265- onSetFilters ( { [ filterId ] : pendingFilters [ filterId ] ?? value } ) ;
266- setPendingFilters ( prev => {
267- // eslint-disable-next-line @typescript-eslint/no-unused-vars
268- const { [ filterId ] : _ , ...rest } = prev ;
269- return rest ;
270- } ) ;
271- } , [ filterId , pendingFilters [ filterId ] ] ) ;
272-
273- return { handleChange, handleClear, handleSearch } ;
274- } ;
275-
276279 // Manually sync to URL in a single effect
277280 useEffect ( ( ) => {
278281 setSearchParams ( params => {
@@ -338,12 +341,23 @@ export function ResourceListDataView<T extends Resource>({
338341
339342 // Determine the active state, errors, and table rows for DataView
340343 const [ activeState , errors , rows ] = useMemo ( ( ) => {
341- if ( isLoading ) {
344+ if ( resourceResult . isLoading ) {
342345 return [ DataViewState . loading , undefined , [ ] ] ;
343346 }
344347
345- if ( listResponse ?. errors ) {
346- return [ DataViewState . error , listResponse . errors , [ ] ] ;
348+ if ( resourceResult ?. error ) {
349+ const e = resourceResult . error ;
350+
351+ if ( e instanceof ApiError ) {
352+ return [ DataViewState . error , e . errors , [ ] ] ;
353+ }
354+
355+ const errObjects = [ {
356+ title : e . message ,
357+ detail : e . toString ( ) ,
358+ } ] ;
359+
360+ return [ DataViewState . error , errObjects , [ ] ] ;
347361 }
348362
349363 if ( listResponse ?. data && listResponse ?. data . length === 0 ) {
@@ -355,7 +369,7 @@ export function ResourceListDataView<T extends Resource>({
355369 [ ] ,
356370 listResponse ?. data ?. map ( entry => rowProvider . callback ( entry ) ) ?? [ ]
357371 ] ;
358- } , [ isLoading , listResponse , ...rowProvider . dependencies ] ) ;
372+ } , [ resourceResult . isLoading , listResponse , ...rowProvider . dependencies ] ) ;
359373
360374 useEffect ( ( ) => {
361375 const pageSize = searchParams . get ( 'page[size]' ) ;
@@ -433,7 +447,7 @@ export function ResourceListDataView<T extends Resource>({
433447 ) ;
434448
435449 const bodyLoading = useMemo (
436- ( ) => < SkeletonTableBody rowsCount = { DEFAULT_PAGE_SIZE } columnsCount = { columns . length } /> ,
450+ ( ) => < SkeletonTableBody rowsCount = { perPage ?? DEFAULT_PAGE_SIZE } columnsCount = { columns . length } /> ,
437451 [ columns . length ]
438452 ) ;
439453
@@ -452,7 +466,12 @@ export function ResourceListDataView<T extends Resource>({
452466 if ( filter . type === 'checkbox' ) {
453467 return < > </ > ; // TODO: Add checkbox filter component
454468 } else {
455- const { handleChange, handleClear, handleSearch } = useTextFilterHandlers ( name ) ;
469+ const { handleChange, handleClear, handleSearch } = useTextFilterHandlers (
470+ name ,
471+ pendingFilters ,
472+ setPendingFilters ,
473+ onSetFilters ,
474+ ) ;
456475 return < DataViewTextFilter
457476 key = { `filter-${ name } ` }
458477 filterId = { name }
0 commit comments