import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { noop } from 'lodash' import { Braces, Calendar, DiamondIcon, Fingerprint, Hash, Key, Link as LinkIcon, ListPlus, MoreVertical, Plus, Search, ToggleRight, Trash, Type, } from 'lucide-react' import { useState } from 'react' import { Button, Card, cn, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import { getColumnTypeAffordance, getForeignKeyColumnNames, getPrimaryKeyColumnNames, getUniqueIndexColumnNames, } from './ColumnList.utils' import { ConstraintToken } from './ConstraintToken' import { displayColumnType } from '@/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils' import AlertError from '@/components/ui/AlertError' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip' import { NoSearchResults } from '@/components/ui/NoSearchResults' import { useTableEditorQuery } from '@/data/table-editor/table-editor-query' import { isTableLike } from '@/data/table-editor/table-editor-types' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import type { SafePostgresColumn } from '@/lib/postgres-types' const getColumnTypeAffordancePresentation = (column: SafePostgresColumn) => { const { kind, label } = getColumnTypeAffordance(column.format) const iconClassName = 'text-foreground-muted' switch (kind) { case 'number': return { icon: , label, } case 'time': return { icon: , label, } case 'text': return { icon: , label, } case 'json': return { icon: , label, } case 'bool': return { icon: , label, } default: return { icon: , label, } } } interface ColumnListProps { onAddColumn: () => void onEditColumn: (column: SafePostgresColumn) => void onDeleteColumn: (column: SafePostgresColumn) => void } export const ColumnList = ({ onAddColumn = noop, onEditColumn = noop, onDeleteColumn = noop, }: ColumnListProps) => { const { id: _id } = useParams() const id = _id ? Number(_id) : undefined const { data: project } = useSelectedProjectQuery() const { data: selectedTable, error, isError, isPending: isLoading, isSuccess, } = useTableEditorQuery({ projectRef: project?.ref, connectionString: project?.connectionString, id, }) const [filterString, setFilterString] = useState('') const isTableEntity = isTableLike(selectedTable) const tableConstraintSource = isTableEntity ? selectedTable : undefined const primaryKeyColumns = getPrimaryKeyColumnNames(tableConstraintSource) const foreignKeyColumns = getForeignKeyColumnNames(tableConstraintSource) const uniqueIndexColumns = getUniqueIndexColumnNames(tableConstraintSource) const columns = (filterString.length === 0 ? (selectedTable?.columns ?? []) : selectedTable?.columns?.filter((column) => column.name.toLowerCase().includes(filterString.toLowerCase()) )) ?? [] const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedTable?.schema ?? '' }) const { can: canUpdateColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) const deleteColumnTooltipText = !canUpdateColumns ? 'Additional permissions required to delete column' : undefined return (
setFilterString(e.target.value)} icon={} />
{!isSchemaLocked && isTableEntity && ( } disabled={!canUpdateColumns} onClick={() => onAddColumn()} tooltip={{ content: { side: 'bottom', text: !canUpdateColumns ? 'You need additional permissions to create columns' : undefined, }, }} > New column )}
{isSchemaLocked && ( )} {isLoading ? (
) : ( Name Type Constraints {isError && ( )} {isSuccess && columns.length === 0 && filterString.length > 0 && ( setFilterString('')} /> )} {isSuccess && columns.length === 0 && filterString.length === 0 && (

No columns created yet

There are no columns in "{selectedTable?.schema}.{selectedTable?.name}"

)} {isSuccess && columns.map((column) => { const { icon: TypeIcon, label: typeLabel } = getColumnTypeAffordancePresentation(column) const constraintTokens = [ primaryKeyColumns.has(column.name) ? ( } label="Primary" variant="primary" /> ) : null, foreignKeyColumns.has(column.name) ? ( } label="Foreign key" /> ) : null, column.is_unique || uniqueIndexColumns.has(column.name) ? ( } label="Unique" /> ) : null, column.is_identity ? ( } label="Identity" /> ) : null, } label={column.is_nullable ? 'Nullable' : 'Non-nullable'} variant="secondary" />, ].filter(Boolean) return (
{TypeIcon}
{column.data_type} {column.format !== column.data_type && ( {displayColumnType( column.format, column.format_schema, column.data_type === 'ARRAY' )} )}

{column.name}

{column.comment !== null ? ( {column.comment} ) : null}

{displayColumnType( column.format, column.format_schema, column.data_type === 'ARRAY' )}

{constraintTokens}
{!isSchemaLocked && isTableEntity && (
onEditColumn(column)} tooltip={{ content: { side: 'bottom', text: !canUpdateColumns ? 'Additional permissions required to edit column' : undefined, }, }} > Edit
)}
) })}
{isSuccess && ( {columns.length} {columns.length === 1 ? 'column' : 'columns'} )}
)}
) }