@@ -38,6 +38,8 @@ export function CatalogDiscoveryWizard({ datasourceId }: Props) {
3838 const [ progress , setProgress ] = useState < string | null > ( null )
3939 const [ activeJobId , setActiveJobId ] = useState < string | null > ( null )
4040 const [ isWorking , setIsWorking ] = useState ( false )
41+ const [ showCatalog , setShowCatalog ] = useState ( false )
42+ const [ catalogViewTable , setCatalogViewTable ] = useState < string | null > ( null )
4143
4244 // AbortController ref for cancelling in-flight SSE streams
4345 const abortRef = useRef < AbortController | null > ( null )
@@ -485,7 +487,160 @@ export function CatalogDiscoveryWizard({ datasourceId }: Props) {
485487 Re-sync
486488 </ button >
487489 ) }
490+
491+ { catalog && catalog . schemas . length > 0 && (
492+ < button
493+ onClick = { ( ) => setShowCatalog ( ( v ) => ! v ) }
494+ className = "px-3 py-1.5 text-sm border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
495+ >
496+ { showCatalog ? 'Hide Catalog ▲' : 'Browse Catalog ▼' }
497+ </ button >
498+ ) }
488499 </ div >
500+
501+ { /* Catalog browser */ }
502+ { showCatalog && catalog && (
503+ < div className = "mt-4 border border-gray-200 rounded-lg overflow-hidden" >
504+ < div className = "flex min-h-[300px] max-h-[60vh]" >
505+ { /* Left panel: table list */ }
506+ < div className = "w-56 flex-shrink-0 border-r border-gray-200 overflow-y-auto" >
507+ { catalog . schemas . map ( ( schema ) => (
508+ < div key = { schema . id } >
509+ < div className = "px-2 py-1.5 bg-gray-50 border-b border-gray-100 flex items-center gap-1.5" >
510+ < span className = "text-xs font-medium text-gray-600 font-mono truncate" >
511+ { schema . schema_name }
512+ </ span >
513+ { schema . schema_alias && (
514+ < span className = "text-xs text-indigo-500 truncate" >
515+ ({ schema . schema_alias } )
516+ </ span >
517+ ) }
518+ { ! schema . is_selected && (
519+ < span className = "ml-auto text-xs text-gray-400 shrink-0" > off</ span >
520+ ) }
521+ </ div >
522+ { schema . tables . map ( ( table ) => {
523+ const key = `${ schema . schema_name } .${ table . table_name } `
524+ const isActive = catalogViewTable === key
525+ return (
526+ < div
527+ key = { table . id }
528+ onClick = { ( ) => setCatalogViewTable ( key ) }
529+ className = { `flex items-center gap-1.5 px-2 py-1.5 cursor-pointer border-l-2 ${
530+ isActive
531+ ? 'bg-indigo-50 border-indigo-500'
532+ : 'border-transparent hover:bg-gray-50'
533+ } ${ ! table . is_selected ? 'opacity-50' : '' } `}
534+ >
535+ < span className = "text-xs font-mono text-gray-800 truncate flex-1" >
536+ { table . table_name }
537+ </ span >
538+ < span
539+ className = { `text-xs px-1 py-0.5 rounded font-medium shrink-0 ${ tableTypeBadgeColor ( table . table_type ) } ` }
540+ >
541+ { tableTypeLabel ( table . table_type ) }
542+ </ span >
543+ { table . is_selected && (
544+ < span className = "text-xs text-green-600 shrink-0" > ✓</ span >
545+ ) }
546+ </ div >
547+ )
548+ } ) }
549+ { schema . tables . length === 0 && (
550+ < div className = "px-2 py-1.5 text-xs text-gray-400 italic" > No tables</ div >
551+ ) }
552+ </ div >
553+ ) ) }
554+ </ div >
555+
556+ { /* Right panel: column detail */ }
557+ < div className = "flex-1 overflow-y-auto" >
558+ { catalogViewTable ? (
559+ ( ( ) => {
560+ const [ schemaName , ...rest ] = catalogViewTable . split ( '.' )
561+ const tableName = rest . join ( '.' )
562+ const schema = catalog . schemas . find ( ( s ) => s . schema_name === schemaName )
563+ const table = schema ?. tables . find ( ( t ) => t . table_name === tableName )
564+ if ( ! table ) {
565+ return (
566+ < div className = "p-3 text-xs text-gray-400" > Table not found.</ div >
567+ )
568+ }
569+ const selectedCount = table . columns . filter (
570+ ( c ) => c . is_selected && c . arrow_type !== null ,
571+ ) . length
572+ const supportedCount = table . columns . filter (
573+ ( c ) => c . arrow_type !== null ,
574+ ) . length
575+ return (
576+ < >
577+ < div className = "px-3 py-2 border-b border-gray-100 flex items-center justify-between bg-white sticky top-0" >
578+ < span className = "text-xs font-semibold font-mono text-gray-800" >
579+ { catalogViewTable }
580+ </ span >
581+ < span className = "text-xs text-gray-500" >
582+ { selectedCount } /{ supportedCount } selected
583+ </ span >
584+ </ div >
585+ < table className = "w-full text-xs border-collapse" >
586+ < thead className = "sticky top-9 bg-white" >
587+ < tr className = "text-gray-500 text-left border-b border-gray-100" >
588+ < th className = "py-1 pl-3 pr-3 font-medium" > Column</ th >
589+ < th className = "py-1 pr-3 font-medium" > Type</ th >
590+ < th className = "py-1 pr-3 font-medium" > Arrow</ th >
591+ < th className = "py-1 pr-3 font-medium" > Null</ th >
592+ < th className = "py-1 pr-3 font-medium" > Sel</ th >
593+ </ tr >
594+ </ thead >
595+ < tbody >
596+ { table . columns
597+ . slice ( )
598+ . sort ( ( a , b ) => a . ordinal_position - b . ordinal_position )
599+ . map ( ( col ) => {
600+ const unsupported = col . arrow_type === null
601+ return (
602+ < tr
603+ key = { col . id }
604+ className = { `border-b border-gray-50 ${ unsupported ? 'opacity-40' : '' } ` }
605+ >
606+ < td className = "py-0.5 pl-3 pr-3 font-mono text-gray-900" >
607+ { col . column_name }
608+ </ td >
609+ < td className = "py-0.5 pr-3 text-gray-600" > { col . data_type } </ td >
610+ < td className = "py-0.5 pr-3 text-gray-500" >
611+ { col . arrow_type ?? (
612+ < span className = "text-yellow-600" > ⚠ unsup</ span >
613+ ) }
614+ </ td >
615+ < td className = "py-0.5 pr-3 text-gray-500" >
616+ { col . is_nullable ? 'yes' : 'no' }
617+ </ td >
618+ < td className = "py-0.5 pr-3" >
619+ { ! unsupported && (
620+ col . is_selected ? (
621+ < span className = "text-green-600 font-medium" > ✓</ span >
622+ ) : (
623+ < span className = "text-gray-300" > —</ span >
624+ )
625+ ) }
626+ </ td >
627+ </ tr >
628+ )
629+ } ) }
630+ </ tbody >
631+ </ table >
632+ </ >
633+ )
634+ } ) ( )
635+ ) : (
636+ < div className = "flex items-center justify-center h-full p-6" >
637+ < p className = "text-sm text-gray-400" > Select a table from the list</ p >
638+ </ div >
639+ ) }
640+ </ div >
641+ </ div >
642+ </ div >
643+ ) }
489644 </ div >
490645 ) }
491646
0 commit comments