mirror of
https://github.com/supabase/supabase.git
synced 2026-07-01 01:25:54 +08:00
Refactor ColumnEditor to use latest UI components + fix some UI oddities (#44214)
## Context Refactors the Table Editor's `ColumnEditor` to use the latest UI components, and fix some UI oddities along the way ## Bug fixes - Fix header text vertical alignment - Before: <img width="325" height="59" alt="image" src="https://github.com/user-attachments/assets/e4bc07d4-2630-4a86-a87c-4bbbf94e2f52" /> - After: <img width="351" height="74" alt="image" src="https://github.com/user-attachments/assets/d0a0a246-59b6-4d19-8674-8cc5eb33772c" /> - Fix closing a toast would close the panel as well - Can verify by creating a new column, then hitting save without entering anything. Will trigger some error toasts and closing them will close the panel too - Fix tooltips on "is nullable" and "is unique" showing up irregardless if "is primary key" is toggled on or off
This commit is contained in:
@@ -18,17 +18,25 @@ import { isEmpty, noop } from 'lodash'
|
||||
import { ExternalLink, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Dictionary } from 'types'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Checkbox_Shadcn_,
|
||||
cn,
|
||||
DialogSectionSeparator,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetSection,
|
||||
SheetTitle,
|
||||
SidePanel,
|
||||
Toggle,
|
||||
Switch,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { ActionBar } from '../ActionBar'
|
||||
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
|
||||
@@ -67,7 +75,7 @@ export interface ColumnEditorProps {
|
||||
existingForeignKeyRelations: ForeignKeyConstraint[]
|
||||
createMore?: boolean
|
||||
},
|
||||
resolve: any
|
||||
resolve: () => void
|
||||
) => void
|
||||
updateEditorDirty: () => void
|
||||
}
|
||||
@@ -83,7 +91,7 @@ export const ColumnEditor = ({
|
||||
const { ref } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
|
||||
const [errors, setErrors] = useState<Dictionary<any>>({})
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({})
|
||||
const [columnFields, setColumnFields] = useState<ColumnField>()
|
||||
const [fkRelations, setFkRelations] = useState<ForeignKey[]>([])
|
||||
const [createMore, setCreateMore] = useState(false)
|
||||
@@ -135,6 +143,7 @@ export const ColumnEditor = ({
|
||||
setColumnFields(columnFields)
|
||||
setFkRelations(formatForeignKeys(foreignKeys))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [visible])
|
||||
|
||||
if (!columnFields) return null
|
||||
@@ -195,7 +204,7 @@ export const ColumnEditor = ({
|
||||
existingForeignKeyRelations: foreignKeys,
|
||||
createMore,
|
||||
}
|
||||
saveChanges(payload, isNewRecord, configuration, (err?: any) => {
|
||||
saveChanges(payload, isNewRecord, configuration, (err?: string) => {
|
||||
resolve()
|
||||
if (!err && createMore && isNewRecord) {
|
||||
const freshColumnFields = generateColumnField({
|
||||
@@ -214,234 +223,295 @@ export const ColumnEditor = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
size="xlarge"
|
||||
key="ColumnEditor"
|
||||
visible={visible}
|
||||
// @ts-ignore
|
||||
onConfirm={(resolve: () => void) => onSaveChanges(resolve)}
|
||||
// @ts-ignore
|
||||
header={<HeaderTitle table={selectedTable} column={column} />}
|
||||
onCancel={closePanel}
|
||||
customFooter={
|
||||
<ActionBar
|
||||
backButtonLabel="Cancel"
|
||||
applyButtonLabel="Save"
|
||||
closePanel={closePanel}
|
||||
applyFunction={(resolve: () => void) => onSaveChanges(resolve)}
|
||||
visible={visible}
|
||||
>
|
||||
{isNewRecord && (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={createMore}
|
||||
onChange={() => setCreateMore(!createMore)}
|
||||
/>
|
||||
<label
|
||||
className="text-foreground-light text-sm cursor-pointer select-none"
|
||||
onClick={() => setCreateMore(!createMore)}
|
||||
<Sheet key="ColumnEditor" open={visible} onOpenChange={(open) => !open && closePanel()}>
|
||||
<SheetContent
|
||||
size="lg"
|
||||
aria-describedby={undefined}
|
||||
className="flex flex-col gap-0"
|
||||
onInteractOutside={(e) => {
|
||||
// Prevent sheet from closing when interacting with toasts
|
||||
const target = e.target as HTMLElement
|
||||
if (target?.closest('[data-sonner-toast]')) e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<SheetHeader>
|
||||
<SheetTitle>
|
||||
<HeaderTitle table={selectedTable} column={column} />
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<SheetSection className="overflow-auto flex-grow p-0">
|
||||
<FormSection
|
||||
header={<FormSectionLabel className="lg:!col-span-4">General</FormSectionLabel>}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
id="name"
|
||||
className={cn(errors.name && '[&>*>label]:text-destructive')}
|
||||
label="Name"
|
||||
description={
|
||||
<>
|
||||
Recommended to use lowercase and use an underscore to separate words e.g.{' '}
|
||||
<code className="text-code-inline">column_name</code>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Create more
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</ActionBar>
|
||||
}
|
||||
>
|
||||
<FormSection header={<FormSectionLabel className="lg:!col-span-4">General</FormSectionLabel>}>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<Input
|
||||
id="name"
|
||||
label="Name"
|
||||
type="text"
|
||||
descriptionText="Recommended to use lowercase and use an underscore to separate words e.g. column_name"
|
||||
placeholder="column_name"
|
||||
error={errors.name}
|
||||
value={columnFields?.name ?? ''}
|
||||
onChange={(event: any) => onUpdateField({ name: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Description"
|
||||
labelOptional="Optional"
|
||||
type="text"
|
||||
value={columnFields?.comment ?? ''}
|
||||
onChange={(event: any) => onUpdateField({ comment: event.target.value })}
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
<SidePanel.Separator />
|
||||
<FormSection
|
||||
header={
|
||||
<FormSectionLabel
|
||||
className="lg:!col-span-4"
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<Button asChild type="default" size="tiny" icon={<Plus strokeWidth={2} />}>
|
||||
<Link href={`/project/${ref}/database/types`} target="_blank" rel="noreferrer">
|
||||
Create enum types
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
type="default"
|
||||
size="tiny"
|
||||
icon={<ExternalLink size={14} strokeWidth={2} />}
|
||||
>
|
||||
<Link
|
||||
href={`${DOCS_URL}/guides/database/tables#data-types`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
About data types
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="column_name"
|
||||
value={columnFields?.name ?? ''}
|
||||
onChange={(event) => onUpdateField({ name: event.target.value })}
|
||||
/>
|
||||
{errors.name && <p className="mt-2 text-destructive">{errors.name}</p>}
|
||||
</FormItemLayout>
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
id="description"
|
||||
label="Description"
|
||||
labelOptional="Optional"
|
||||
>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
value={columnFields?.comment ?? ''}
|
||||
onChange={(event) => onUpdateField({ comment: event.target.value })}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<FormSection
|
||||
header={
|
||||
<FormSectionLabel
|
||||
className="lg:!col-span-4"
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<Button asChild type="default" icon={<Plus />}>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`/project/${ref}/database/types`}
|
||||
>
|
||||
Create enum types
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`${DOCS_URL}/guides/database/tables#data-types`}
|
||||
>
|
||||
About data types
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Data Type
|
||||
</FormSectionLabel>
|
||||
}
|
||||
>
|
||||
Data Type
|
||||
</FormSectionLabel>
|
||||
}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<ColumnType
|
||||
showRecommendation
|
||||
value={columnFields?.format ?? ''}
|
||||
layout="vertical"
|
||||
enumTypes={enumTypes}
|
||||
error={errors.format}
|
||||
description={
|
||||
lockColumnType ? 'Column type cannot be changed as it has a foreign key relation' : ''
|
||||
}
|
||||
disabled={lockColumnType}
|
||||
onOptionSelect={(format: string) => onUpdateField({ format, defaultValue: null })}
|
||||
/>
|
||||
{columnFields.foreignKey === undefined && (
|
||||
<div className="space-y-4">
|
||||
{columnFields.format.includes('int') && (
|
||||
<div className="w-full">
|
||||
<Checkbox
|
||||
label="Is Identity"
|
||||
description="Automatically assign a sequential unique number to the column"
|
||||
checked={columnFields.isIdentity}
|
||||
onChange={() => {
|
||||
const isIdentity = !columnFields.isIdentity
|
||||
const isArray = isIdentity ? false : columnFields.isArray
|
||||
onUpdateField({ isIdentity, isArray })
|
||||
}}
|
||||
/>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<ColumnType
|
||||
showRecommendation
|
||||
value={columnFields?.format ?? ''}
|
||||
layout="vertical"
|
||||
enumTypes={enumTypes}
|
||||
error={errors.format}
|
||||
description={
|
||||
lockColumnType
|
||||
? 'Column type cannot be changed as it has a foreign key relation'
|
||||
: ''
|
||||
}
|
||||
disabled={lockColumnType}
|
||||
onOptionSelect={(format: string) => onUpdateField({ format, defaultValue: null })}
|
||||
/>
|
||||
{columnFields.foreignKey === undefined && (
|
||||
<div className="space-y-4">
|
||||
{columnFields.format.includes('int') && (
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="flex"
|
||||
label="Is Identity"
|
||||
id="isIdentity"
|
||||
description="Automatically assign a sequential unique number to the column"
|
||||
>
|
||||
<Checkbox_Shadcn_
|
||||
id="isIdentity"
|
||||
checked={columnFields.isIdentity}
|
||||
onCheckedChange={() => {
|
||||
const isIdentity = !columnFields.isIdentity
|
||||
const isArray = isIdentity ? false : columnFields.isArray
|
||||
onUpdateField({ isIdentity, isArray })
|
||||
}}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
{!columnFields.isPrimaryKey && (
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="flex"
|
||||
id="isArray"
|
||||
label="Define as Array"
|
||||
description="Allow column to be defined as variable-length multidimensional arrays"
|
||||
>
|
||||
<Checkbox_Shadcn_
|
||||
id="isArray"
|
||||
checked={columnFields.isArray}
|
||||
onCheckedChange={() => {
|
||||
const isArray = !columnFields.isArray
|
||||
const isIdentity = isArray ? false : columnFields.isIdentity
|
||||
onUpdateField({ isArray, isIdentity })
|
||||
}}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!columnFields.isPrimaryKey && (
|
||||
<div className="w-full">
|
||||
<Checkbox
|
||||
label="Define as Array"
|
||||
description="Allow column to be defined as variable-length multidimensional arrays"
|
||||
checked={columnFields.isArray}
|
||||
onChange={() => {
|
||||
const isArray = !columnFields.isArray
|
||||
const isIdentity = isArray ? false : columnFields.isIdentity
|
||||
onUpdateField({ isArray, isIdentity })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ColumnDefaultValue
|
||||
columnFields={columnFields}
|
||||
enumTypes={enumTypes}
|
||||
onUpdateField={onUpdateField}
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
<ColumnDefaultValue
|
||||
columnFields={columnFields}
|
||||
enumTypes={enumTypes}
|
||||
onUpdateField={onUpdateField}
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
|
||||
<SidePanel.Separator />
|
||||
<SidePanel.Separator />
|
||||
|
||||
<FormSection
|
||||
header={<FormSectionLabel className="lg:!col-span-4">Foreign Keys</FormSectionLabel>}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<ColumnForeignKey
|
||||
tableId={selectedTable.id}
|
||||
column={columnFields}
|
||||
relations={fkRelations}
|
||||
<FormSection
|
||||
header={<FormSectionLabel className="lg:!col-span-4">Foreign Keys</FormSectionLabel>}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<ColumnForeignKey
|
||||
tableId={selectedTable.id}
|
||||
column={columnFields}
|
||||
relations={fkRelations}
|
||||
closePanel={closePanel}
|
||||
onUpdateColumnType={(format: string) => {
|
||||
if (format[0] === '_') {
|
||||
onUpdateField({ format: format.slice(1), isArray: true, isIdentity: false })
|
||||
} else {
|
||||
onUpdateField({ format })
|
||||
}
|
||||
}}
|
||||
onUpdateFkRelations={setFkRelations}
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
<SidePanel.Separator />
|
||||
<FormSection
|
||||
header={<FormSectionLabel className="lg:!col-span-4">Constraints</FormSectionLabel>}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="flex"
|
||||
id="isPrimaryKey"
|
||||
label="Is Primary Key"
|
||||
description="A primary key indicates that a column or group of columns can be used as a unique identifier for rows in the table"
|
||||
>
|
||||
<Switch
|
||||
id="isPrimaryKey"
|
||||
checked={columnFields?.isPrimaryKey ?? false}
|
||||
onCheckedChange={() =>
|
||||
onUpdateField({
|
||||
isPrimaryKey: !columnFields?.isPrimaryKey,
|
||||
isUnique: false,
|
||||
isNullable: false,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="flex"
|
||||
id="isNullable"
|
||||
label="Allow Nullable"
|
||||
description="Allow the column to assume a NULL value if no value is provided"
|
||||
>
|
||||
<Switch
|
||||
id="isNullable"
|
||||
disabled={columnFields.isPrimaryKey}
|
||||
checked={columnFields.isNullable}
|
||||
onCheckedChange={() =>
|
||||
onUpdateField({ isNullable: !columnFields.isNullable })
|
||||
}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
</TooltipTrigger>
|
||||
{columnFields.isPrimaryKey && (
|
||||
<TooltipContent side="left" align="start">
|
||||
Column is a primary key and hence cannot be NULL
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<FormItemLayout
|
||||
isReactForm={false}
|
||||
layout="flex"
|
||||
id="isUnique"
|
||||
label="Is Unique"
|
||||
description="Enforce values in the column to be unique across rows"
|
||||
>
|
||||
<Switch
|
||||
id="isUnique"
|
||||
disabled={columnFields.isPrimaryKey}
|
||||
checked={columnFields.isUnique}
|
||||
onCheckedChange={() => onUpdateField({ isUnique: !columnFields.isUnique })}
|
||||
/>
|
||||
</FormItemLayout>
|
||||
</TooltipTrigger>
|
||||
{columnFields.isPrimaryKey && (
|
||||
<TooltipContent side="left" align="start">
|
||||
Column is a primary key and hence already unique
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<FormItemLayout isReactForm={false} label="CHECK constraint" labelOptional="Optional">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
value={columnFields?.check ?? ''}
|
||||
onChange={(event) => onUpdateField({ check: event.target.value })}
|
||||
className="[&_input]:font-mono"
|
||||
/>
|
||||
</FormItemLayout>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
</SheetSection>
|
||||
|
||||
<SheetFooter className="!justify-between [&>div]:p-0 [&>div]:border-t-0">
|
||||
<ActionBar
|
||||
backButtonLabel="Cancel"
|
||||
applyButtonLabel="Save"
|
||||
closePanel={closePanel}
|
||||
onUpdateColumnType={(format: string) => {
|
||||
if (format[0] === '_') {
|
||||
onUpdateField({ format: format.slice(1), isArray: true, isIdentity: false })
|
||||
} else {
|
||||
onUpdateField({ format })
|
||||
}
|
||||
}}
|
||||
onUpdateFkRelations={setFkRelations}
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
<SidePanel.Separator />
|
||||
<FormSection
|
||||
header={<FormSectionLabel className="lg:!col-span-4">Constraints</FormSectionLabel>}
|
||||
>
|
||||
<FormSectionContent loading={false} className="lg:!col-span-8">
|
||||
<Toggle
|
||||
label="Is Primary Key"
|
||||
descriptionText="A primary key indicates that a column or group of columns can be used as a unique identifier for rows in the table"
|
||||
checked={columnFields?.isPrimaryKey ?? false}
|
||||
onChange={() =>
|
||||
onUpdateField({
|
||||
isPrimaryKey: !columnFields?.isPrimaryKey,
|
||||
isUnique: false,
|
||||
isNullable: false,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Toggle
|
||||
label="Allow Nullable"
|
||||
disabled={columnFields.isPrimaryKey}
|
||||
descriptionText="Allow the column to assume a NULL value if no value is provided"
|
||||
checked={columnFields.isNullable}
|
||||
onChange={() => onUpdateField({ isNullable: !columnFields.isNullable })}
|
||||
/>
|
||||
applyFunction={(resolve: () => void) => onSaveChanges(resolve)}
|
||||
visible={visible}
|
||||
>
|
||||
{isNewRecord && (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Switch checked={createMore} onCheckedChange={() => setCreateMore(!createMore)} />
|
||||
<label
|
||||
className="text-foreground-light text-sm cursor-pointer select-none"
|
||||
onClick={() => setCreateMore(!createMore)}
|
||||
>
|
||||
Create more
|
||||
</label>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left" align="start">
|
||||
Column is a primary key and hence cannot be NULL
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Toggle
|
||||
label="Is Unique"
|
||||
disabled={columnFields.isPrimaryKey}
|
||||
descriptionText="Enforce values in the column to be unique across rows"
|
||||
checked={columnFields.isUnique}
|
||||
onChange={() => onUpdateField({ isUnique: !columnFields.isUnique })}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left" align="start">
|
||||
Column is a primary key and hence already unique
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Input
|
||||
label="CHECK Constraint"
|
||||
labelOptional="Optional"
|
||||
placeholder={placeholder}
|
||||
type="text"
|
||||
value={columnFields?.check ?? ''}
|
||||
onChange={(event: any) => onUpdateField({ check: event.target.value })}
|
||||
className="[&_input]:font-mono"
|
||||
/>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
</SidePanel>
|
||||
)}
|
||||
</ActionBar>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
interface Props {
|
||||
table: PostgresTable
|
||||
column: PostgresColumn
|
||||
column?: PostgresColumn
|
||||
}
|
||||
|
||||
export const HeaderTitle = ({ table, column }: Props) => {
|
||||
|
||||
@@ -7,21 +7,21 @@ interface HeaderTitleProps {
|
||||
export const HeaderTitle = ({ schema, table, isDuplicating }: HeaderTitleProps) => {
|
||||
if (!table) {
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
Create a new table under <code className="text-code-inline !text-sm">{schema}</code>
|
||||
</>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
if (isDuplicating) {
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
Duplicate table <code className="text-code-inline !text-sm">{table?.name}</code>
|
||||
</>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
Update table <code className="text-code-inline !text-sm">{table?.name}</code>
|
||||
</>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user