import { FC, useEffect, useState } from 'react' import Link from 'next/link' import { partition } from 'lodash' import { observer } from 'mobx-react-lite' import { Button, Dropdown, IconChevronDown, IconCopy, IconEdit, IconLoader, IconLock, IconRefreshCw, IconSearch, IconTrash, IconX, Input, Listbox, Menu, } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' import type { PostgresSchema, PostgresTable } from '@supabase/postgres-meta' import { SchemaView } from 'types' import { checkPermissions, useStore, useParams } from 'hooks' import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' interface Props { selectedSchema?: string onSelectSchema: (schema: string) => void onAddTable: () => void onEditTable: (table: PostgresTable) => void onDeleteTable: (table: PostgresTable) => void onDuplicateTable: (table: PostgresTable) => void } const TableEditorMenu: FC = ({ selectedSchema, onSelectSchema = () => {}, onAddTable = () => {}, onEditTable = () => {}, onDeleteTable = () => {}, onDuplicateTable = () => {}, }) => { const { meta } = useStore() const { id, ref } = useParams() const schemas: PostgresSchema[] = meta.schemas.list() const tables: PostgresTable[] = meta.tables.list( (table: PostgresTable) => table.schema === selectedSchema ) const views: SchemaView[] = meta.views.list((view: SchemaView) => view.schema === selectedSchema) const foreignTables: Partial[] = meta.foreignTables.list( (table: Partial) => table.schema === selectedSchema ) const isFetchingTables = // @ts-ignore tables.filter((t) => t.schema === selectedSchema).length === 0 && meta.tables.isLoading const schema = schemas.find((schema) => schema.name === selectedSchema) const canCreateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') const [searchText, setSearchText] = useState('') const [isRefreshing, setIsRefreshing] = useState(false) const refreshTables = async () => { if (selectedSchema) { setIsRefreshing(true) await meta.tables.loadBySchema(selectedSchema) await meta.views.loadBySchema(selectedSchema) setIsRefreshing(false) } } const filteredTables = searchText.length === 0 ? tables : // @ts-ignore tables.filter((table) => table.name.toLowerCase().includes(searchText.toLowerCase())) const filteredViews = searchText.length === 0 ? views || [] : (views || []).filter((view: Partial) => (view?.name ?? '').toLowerCase().includes(searchText.toLowerCase()) ) const filteredForeignTables = searchText.length === 0 ? foreignTables : foreignTables.filter((table) => (table?.name ?? '').toLowerCase().includes(searchText.toLowerCase()) ) const [protectedSchemas, openSchemas] = partition(schemas, (schema) => meta.excludedSchemas.includes(schema?.name ?? '') ) const isLocked = protectedSchemas.some((s) => s.id === schema?.id) return (
{/* Schema selection dropdown */}
{!meta.schemas.isInitialized ? (
Loading schemas...
) : ( { setSearchText('') onSelectSchema(name) }} >

Schemas

{/* @ts-ignore */} {openSchemas.map((schema) => ( schema} > {schema.name} ))}

Protected schemas

{protectedSchemas.map((schema) => ( schema} > {schema.name} ))}
)}
{!isLocked && (
{/* Add new table button */}
} type="default" style={{ justifyContent: 'start' }} onClick={onAddTable} > New table {!canCreateTables && (
You need additional permissions to create tables
)}
)} {/* Table search input */}
} placeholder="Search tables" onChange={(e) => setSearchText(e.target.value)} value={searchText} size="tiny" actions={ searchText && ( ) } />
{isFetchingTables ? (

Loading tables...

) : searchText.length === 0 && filteredTables.length === 0 && filteredViews.length === 0 ? (

No tables available

This schema has no tables available yet

) : (
{/* List of tables belonging to selected schema */} {filteredTables.length > 0 && (

Tables

({filteredTables.length})

} />
{filteredTables.map((table) => { const isActive = Number(id) === table.id return ( } onClick={() => onEditTable(table)} > Edit Table , } onClick={() => onDuplicateTable(table)} > Duplicate Table , }> View Policies , , } onClick={() => onDeleteTable(table)} > Delete Table , ]} >
) } /> ) })}
)} {/* List of views belonging to selected schema */} {filteredViews.length > 0 && (

Views

({filteredViews.length})

} /> {filteredViews.map((view: SchemaView) => { const isActive = Number(id) === view.id return (

{view.name}

) })} )} {/* List of foreign tables belonging to selected schema */} {filteredForeignTables.length > 0 && (

Foreign Tables

({filteredForeignTables.length})

} /> {filteredForeignTables.map((table: Partial) => { const isActive = Number(id) === table.id return (

{table.name}

) })}
)} )} {searchText.length > 0 && filteredTables.length === 0 && filteredViews.length === 0 && (

No results found

There are no tables or views that match your search

)} ) } export default observer(TableEditorMenu)