@@ -161,6 +161,9 @@ function MatchedVariablesDataGrid({
161161 const [ isApiReady , setIsApiReady ] = useState ( false ) ;
162162 const [ totalVariableCount , setTotalVariableCount ] = useState < number | null > ( null ) ;
163163 const [ shouldHideTable , setShouldHideTable ] = useState ( false ) ;
164+ const [ currentSearchQuery , setCurrentSearchQuery ] = useState < string > ( "" ) ;
165+ const [ hasUrlsFromServer , setHasUrlsFromServer ] = useState ( false ) ;
166+ const [ hasResponseOptionsFromServer , setHasResponseOptionsFromServer ] = useState ( false ) ;
164167
165168 // Debouncing for search queries
166169 const debounceTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
@@ -198,9 +201,9 @@ function MatchedVariablesDataGrid({
198201 return {
199202 getRows : async ( params : any ) => {
200203 // Extract search query from filterModel (QuickFilter sets this)
201- // QuickFilter uses the "quickFilterValues" in filterModel
202- const quickFilterValue = params . filterModel ?. quickFilterValues ?. [ 0 ] || "" ;
203- const searchQuery = quickFilterValue ;
204+ // QuickFilter splits by spaces, so we need to join all values back together
205+ const quickFilterValues = params . filterModel ?. quickFilterValues || [ ] ;
206+ const searchQuery = quickFilterValues . join ( " " ) . trim ( ) ;
204207
205208 // Handle paginationModel (can be undefined in some cases)
206209 const paginationModel = params . paginationModel || { page : 0 , pageSize : 50 } ;
@@ -257,6 +260,9 @@ function MatchedVariablesDataGrid({
257260 sortOrder = `${ field } :${ direction } ` ;
258261 }
259262
263+ // Update current search query for empty state display
264+ setCurrentSearchQuery ( searchQuery || "" ) ;
265+
260266 // Build fetch options - only include query/search params if we have a query
261267 const fetchOptions : Parameters < typeof fetchVariables > [ 0 ] = {
262268 // Use ancestor_uuid for all cases (works for both top-level studies and child datasets)
@@ -283,43 +289,39 @@ function MatchedVariablesDataGrid({
283289 let response = await fetchVariables ( fetchOptions ) ;
284290 let results = response . results || [ ] ;
285291
286- // If no results and we have a search query, try again without the query
287- // Only do this on the first page (offset 0) to avoid retrying on every page
288- if ( results . length === 0 &&
289- searchQuery &&
290- searchQuery . trim ( ) &&
291- paginationModel . page === 0 &&
292- paginationModel . pageSize > 0 ) {
293- const retryOptions : Parameters < typeof fetchVariables > [ 0 ] = {
294- ...fetchOptions ,
295- query : undefined , // Remove query for retry
296- } ;
297-
298- response = await fetchVariables ( retryOptions ) ;
299- results = response . results || [ ] ;
300-
301- // If still no results, hide the table
302- if ( results . length === 0 && ( response . num_hits === 0 || response . num_hits === undefined ) ) {
303- setShouldHideTable ( true ) ;
304- onShouldHideTable ?.( true ) ;
305- } else {
292+ // Check for URLs and response options in the server response
293+ // Only check on first page to avoid unnecessary updates
294+ if ( paginationModel . page === 0 ) {
295+ const hasUrls = results . some ( ( r : any ) =>
296+ r . urls && Array . isArray ( r . urls ) && r . urls . length > 0
297+ ) ;
298+ const hasResponseOptions = results . some ( ( r : any ) => {
299+ const options = r . response_options || r . options ;
300+ return options && Array . isArray ( options ) && options . length > 0 ;
301+ } ) ;
302+ setHasUrlsFromServer ( hasUrls ) ;
303+ setHasResponseOptionsFromServer ( hasResponseOptions ) ;
304+ }
305+
306+ // Check if we should hide the table on first page with no results
307+ // Only hide if there's no query - if there's a query, show empty state instead
308+ if ( paginationModel . page === 0 &&
309+ results . length === 0 &&
310+ ( response . num_hits === 0 || response . num_hits === undefined ) ) {
311+ // If there's a search query, show the table with empty state
312+ // Otherwise, hide the table (no variables at all)
313+ if ( searchQuery && searchQuery . trim ( ) ) {
306314 setShouldHideTable ( false ) ;
307315 onShouldHideTable ?.( false ) ;
308- }
309- } else {
310- // Check if we should hide the table on first page with no results
311- // This applies whether or not there's a search query
312- if ( paginationModel . page === 0 &&
313- results . length === 0 &&
314- ( response . num_hits === 0 || response . num_hits === undefined ) ) {
315- // No results on first page - hide the table
316+ } else {
317+ // No results and no query - hide the table
316318 setShouldHideTable ( true ) ;
317319 onShouldHideTable ?.( true ) ;
318- } else {
319- // We have results or there are more results available - show the table
320- setShouldHideTable ( false ) ;
321- onShouldHideTable ?.( false ) ;
322320 }
321+ } else {
322+ // We have results or there are more results available - show the table
323+ setShouldHideTable ( false ) ;
324+ onShouldHideTable ?.( false ) ;
323325 }
324326
325327 // Create a Set of matched variable names for quick lookup
@@ -527,19 +529,31 @@ function MatchedVariablesDataGrid({
527529 } , [ ] ) ;
528530
529531 // Check if any variables have response options data
532+ // Use server data if available (server-side mode), otherwise check props variables (client-side mode)
530533 const hasResponseOptions = useMemo ( ( ) => {
534+ if ( studyUuid ) {
535+ // Server-side mode - use state from API response
536+ return hasResponseOptionsFromServer ;
537+ }
538+ // Client-side mode - check props variables
531539 return variables . some ( ( v ) => {
532540 const responseOptions = v . response_options || v . options ;
533541 return responseOptions && Array . isArray ( responseOptions ) && responseOptions . length > 0 ;
534542 } ) ;
535- } , [ variables ] ) ;
536-
543+ } , [ studyUuid , hasResponseOptionsFromServer , variables ] ) ;
544+
537545 // Check if any variables have URLs
546+ // Use server data if available (server-side mode), otherwise check props variables (client-side mode)
538547 const hasUrls = useMemo ( ( ) => {
548+ if ( studyUuid ) {
549+ // Server-side mode - use state from API response
550+ return hasUrlsFromServer ;
551+ }
552+ // Client-side mode - check props variables
539553 return variables . some ( ( v ) => {
540554 return v . urls && Array . isArray ( v . urls ) && v . urls . length > 0 ;
541555 } ) ;
542- } , [ variables ] ) ;
556+ } , [ studyUuid , hasUrlsFromServer , variables ] ) ;
543557
544558 const columns : GridColDef [ ] = useMemo ( ( ) => {
545559 const baseColumns : GridColDef [ ] = [
@@ -590,11 +604,12 @@ function MatchedVariablesDataGrid({
590604 if ( hasUrls ) {
591605 baseColumns . push ( {
592606 field : "url" ,
593- headerName : "" ,
594- width : 60 ,
607+ headerName : "Learn more " ,
608+ width : 120 ,
595609 sortable : false ,
596610 renderCell : ( params : any ) => {
597- const variable = variables [ params . row . originalIndex ?? 0 ] ;
611+ // Get variable data from row (works for both client-side and server-side)
612+ const variable = params . row ;
598613 const firstUrl = variable ?. urls && Array . isArray ( variable . urls ) && variable . urls . length > 0
599614 ? variable . urls [ 0 ]
600615 : null ;
@@ -895,6 +910,24 @@ function MatchedVariablesDataGrid({
895910 showToolbar
896911 initialState = { initialState }
897912 slots = { {
913+ noRowsOverlay : ( ) => (
914+ < Box
915+ sx = { {
916+ display : "flex" ,
917+ flexDirection : "column" ,
918+ alignItems : "center" ,
919+ justifyContent : "center" ,
920+ height : "100%" ,
921+ p : 4 ,
922+ } }
923+ >
924+ < Typography variant = "body1" color = "text.secondary" >
925+ { currentSearchQuery && currentSearchQuery . trim ( )
926+ ? "No variables match your query. Remove it to see them all."
927+ : "No variables found." }
928+ </ Typography >
929+ </ Box >
930+ ) ,
898931 toolbar : ( ) => (
899932 < Box
900933 sx = { {
0 commit comments