TableSearchResults.tsx 4.3 KB

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