import { PermissionAction } from '@supabase/shared-types/out/constants' import { keepPreviousData } from '@tanstack/react-query' import { useParams } from 'common' import { Filter, Plus } from 'lucide-react' import { useCallback, useEffect, useMemo, useState } from 'react' import { Button, Checkbox, Label_Shadcn_, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, } from 'ui' import { InnerSideBarEmptyPanel, InnerSideBarFilters, InnerSideBarFilterSearchInput, InnerSideBarFilterSortDropdown, InnerSideBarFilterSortDropdownItem, } from 'ui-patterns/InnerSideMenu' import { useTableEditorTabsCleanUp } from '../Tabs/Tabs.utils' import { EntityListItem } from './EntityListItem' import { TableMenuEmptyState } from './TableMenuEmptyState' import { ExportDialog } from '@/components/grid/components/header/ExportDialog' import { parseSupaTable } from '@/components/grid/SupabaseGrid.utils' import { SupaTable } from '@/components/grid/types' import { ProtectedSchemaWarning } from '@/components/interfaces/Database/ProtectedSchemaWarning' import { ErrorMatcher } from '@/components/interfaces/ErrorHandling/ErrorMatcher' import EditorMenuListSkeleton from '@/components/layouts/TableEditorLayout/EditorMenuListSkeleton' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { InfiniteListDefault, LoaderForIconMenuItems } from '@/components/ui/InfiniteList' import SchemaSelector from '@/components/ui/SchemaSelector' import { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants' import { useEntityTypesQuery } from '@/data/entity-types/entity-types-infinite-query' import { useTableApiAccessQuery } from '@/data/privileges/table-api-access-query' import { getTableEditor, useTableEditorQuery } from '@/data/table-editor/table-editor-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorage } from '@/hooks/misc/useLocalStorage' import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import { useTableEditorStateSnapshot } from '@/state/table-editor' export const TableEditorMenu = () => { const { id: _id, ref: projectRef } = useParams() const id = _id ? Number(_id) : undefined const snap = useTableEditorStateSnapshot() const { selectedSchema, setSelectedSchema } = useQuerySchemaState() const [searchText, setSearchText] = useState('') const [tableToExport, setTableToExport] = useState() const [visibleTypes, setVisibleTypes] = useState(Object.values(ENTITY_TYPE)) const [sort, setSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>( 'table-editor-sort', 'alphabetical' ) const { data: project } = useSelectedProjectQuery() const { data, isLoading, isSuccess, isError, error, hasNextPage, isFetchingNextPage, fetchNextPage, } = useEntityTypesQuery( { projectRef: project?.ref, connectionString: project?.connectionString, schemas: [selectedSchema], search: searchText.trim() || undefined, sort, filterTypes: visibleTypes, }, { placeholderData: Boolean(searchText) ? keepPreviousData : undefined, } ) const entityTypes = useMemo( () => data?.pages.flatMap((page) => page.data.entities), [data?.pages] ) const entityNames = useMemo(() => entityTypes?.map((entity) => entity.name) ?? [], [entityTypes]) const { data: apiAccessByTableName } = useTableApiAccessQuery( { projectRef: project?.ref, connectionString: project?.connectionString ?? undefined, schemaName: selectedSchema, tableNames: entityNames, }, { enabled: Boolean(selectedSchema && entityNames.length > 0) } ) const { can: canCreateTables } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema }) const { data: selectedTable } = useTableEditorQuery({ projectRef: project?.ref, connectionString: project?.connectionString, id, }) if (selectedTable?.schema && !selectedSchema) { setSelectedSchema(selectedTable.schema) } const tableEditorTabsCleanUp = useTableEditorTabsCleanUp() const onSelectExportCLI = useCallback( async (id: number) => { const table = await getTableEditor({ id: id, projectRef, connectionString: project?.connectionString, }) const supaTable = table && parseSupaTable(table) setTableToExport(supaTable) }, [project?.connectionString, projectRef] ) const getItemKey = useCallback( (index: number) => { const item = entityTypes?.[index] return item?.id ? String(item.id) : `table-editor-entity-${index}` }, [entityTypes] ) const entityProps = useMemo( () => ({ projectRef: project?.ref!, id: Number(id), isLocked: isSchemaLocked, onExportCLI: () => onSelectExportCLI(Number(id)), apiAccessMap: apiAccessByTableName, }), [project?.ref, id, isSchemaLocked, onSelectExportCLI, apiAccessByTableName] ) useEffect(() => { // Clean up tabs + recent items for any tables that might have been removed outside of the dashboard session if (entityTypes && !searchText) { tableEditorTabsCleanUp({ schemas: [selectedSchema], entities: entityTypes }) } }, [entityTypes, searchText, selectedSchema, tableEditorTabsCleanUp]) return ( <>
{ setSearchText('') setSelectedSchema(name) }} onSelectCreateSchema={() => snap.onAddSchema()} />
{!isSchemaLocked ? ( } type="default" className="justify-start" onClick={() => snap.onAddTable()} tooltip={{ content: { side: 'bottom', text: !canCreateTables ? 'You need additional permissions to create tables' : undefined, }, }} > New table ) : ( )}
setSearchText(e.target.value)} > setSort(value)} > Alphabetical Entity Type
))}
{isLoading && } {isError && ( )} {isSuccess && ( <> {searchText.length === 0 && (entityTypes?.length ?? 0) <= 0 && ( )} {searchText.length > 0 && (entityTypes?.length ?? 0) <= 0 && ( )} {(entityTypes?.length ?? 0) > 0 && (
index !== 0 && index === entityTypes!.length ? 85 : 28 } hasNextPage={hasNextPage} isLoadingNextPage={isFetchingNextPage} onLoadNextPage={fetchNextPage} />
)} )} { if (!open) setTableToExport(undefined) }} /> ) }