TableSearchResults.tsx154 lines · main
1'use client'
2
3import { useParams } from 'common'
4import { Database, Loader2 } from 'lucide-react'
5import { useMemo } from 'react'
6
7import {
8 EmptyState,
9 ResultsList,
10 SkeletonResults,
11 type SearchResult,
12} from './ContextSearchResults.shared'
13import { useTablesQuery } from '@/data/tables/tables-query'
14import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
15
16interface TableSearchResultsProps {
17 query: string
18}
19
20export function TableSearchResults({ query }: TableSearchResultsProps) {
21 const { ref: projectRef } = useParams()
22 const { data: project } = useSelectedProjectQuery()
23
24 const trimmedQuery = query.trim()
25
26 const {
27 data: tables,
28 isLoading: isLoadingTables,
29 isError: isErrorTables,
30 } = useTablesQuery(
31 {
32 projectRef: project?.ref,
33 connectionString: project?.connectionString,
34 includeColumns: false,
35 sortByProperty: 'name',
36 },
37 {
38 enabled: !!project?.ref,
39 }
40 )
41
42 const tableResults: SearchResult[] = useMemo(() => {
43 if (!tables) return []
44
45 const filtered = trimmedQuery
46 ? tables.filter((table) => {
47 const searchLower = trimmedQuery.toLowerCase()
48 const tableName = table.name?.toLowerCase() || ''
49 const schemaName = table.schema?.toLowerCase() || ''
50 const fullName = `${schemaName}.${tableName}`
51
52 return (
53 tableName.includes(searchLower) ||
54 schemaName.includes(searchLower) ||
55 fullName.includes(searchLower)
56 )
57 })
58 : tables
59
60 // Limit results for performance
61 return filtered.slice(0, 20).map((table) => {
62 const displayName =
63 table.schema && table.schema !== 'public'
64 ? `${table.schema}.${table.name}`
65 : table.name || 'Untitled Table'
66
67 const description = table.comment
68 ? table.comment.length > 50
69 ? `${table.comment.slice(0, 50)}...`
70 : table.comment
71 : undefined
72
73 return {
74 id: String(table.id),
75 name: displayName,
76 description,
77 }
78 })
79 }, [tables, trimmedQuery])
80
81 const totalTables = tables?.length ?? 0
82
83 const renderFooter = () => (
84 <div className="absolute bottom-0 left-0 right-0 flex items-center justify-between min-h-9 h-9 px-4 border-t bg-surface-200 text-xs text-foreground-light z-10">
85 <div className="flex items-center gap-x-2">
86 {isLoadingTables ? (
87 <span className="flex items-center gap-2">
88 <Loader2 size={14} className="animate-spin" /> Loading...
89 </span>
90 ) : (
91 <span>
92 Total: {totalTables.toLocaleString()} table{totalTables !== 1 ? 's' : ''}
93 </span>
94 )}
95 </div>
96 </div>
97 )
98
99 if (isLoadingTables) {
100 return (
101 <div className="relative h-full flex flex-col">
102 <div className="flex-1 min-h-0 overflow-hidden">
103 <SkeletonResults />
104 </div>
105 {renderFooter()}
106 </div>
107 )
108 }
109
110 if (isErrorTables) {
111 return (
112 <div className="relative h-full flex flex-col">
113 <div className="flex-1 min-h-0 overflow-hidden">
114 <div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
115 <Database className="h-6 w-6" strokeWidth={1.5} />
116 <p className="text-sm">Failed to load tables</p>
117 </div>
118 </div>
119 {renderFooter()}
120 </div>
121 )
122 }
123
124 if (tableResults.length === 0) {
125 return (
126 <div className="relative h-full flex flex-col">
127 <div className="flex-1 min-h-0 overflow-hidden">
128 <EmptyState icon={Database} label="Database Tables" query={query} />
129 </div>
130 {renderFooter()}
131 </div>
132 )
133 }
134
135 return (
136 <div className="relative h-full flex flex-col">
137 <div className="flex-1 min-h-0 overflow-hidden">
138 <ResultsList
139 results={tableResults}
140 icon={Database}
141 getRoute={(result) => {
142 const table = tables?.find((t) => String(t.id) === result.id)
143 if (!table || !projectRef) return `/project/${projectRef}/editor` as `/${string}`
144
145 const schemaParam = table.schema ? `?schema=${table.schema}` : ''
146 return `/project/${projectRef}/editor/${table.id}${schemaParam}` as `/${string}`
147 }}
148 className="pb-9"
149 />
150 </div>
151 {renderFooter()}
152 </div>
153 )
154}