1- import { noop } from 'lodash'
2- import { Checkbox , IconMenu , IconSettings , IconX , Input , Popover } from 'ui'
1+ import { Link , Menu , Plus , Settings , X } from 'lucide-react'
2+ import {
3+ Badge ,
4+ Button ,
5+ Checkbox ,
6+ CommandGroup_Shadcn_ ,
7+ CommandItem_Shadcn_ ,
8+ CommandList_Shadcn_ ,
9+ CommandSeparator_Shadcn_ ,
10+ Command_Shadcn_ ,
11+ Input ,
12+ Popover ,
13+ PopoverContent_Shadcn_ ,
14+ PopoverTrigger_Shadcn_ ,
15+ Popover_Shadcn_ ,
16+ cn ,
17+ } from 'ui'
318
19+ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
20+ import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
421import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
522import { EMPTY_ARR , EMPTY_OBJ } from 'lib/void'
23+ import { useState } from 'react'
624import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants'
725import type { Suggestion } from '../ColumnEditor/ColumnEditor.types'
826import ColumnType from '../ColumnEditor/ColumnType'
927import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions'
28+ import { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
1029import type { ColumnField } from '../SidePanelEditor.types'
30+ import { checkIfRelationChanged } from './ForeignKeysManagement/ForeignKeysManagement.utils'
1131
1232/**
1333 * [Joshen] For context:
@@ -29,25 +49,31 @@ import type { ColumnField } from '../SidePanelEditor.types'
2949
3050interface ColumnProps {
3151 column : ColumnField
52+ relations : ForeignKey [ ]
3253 enumTypes : EnumeratedType [ ]
3354 isNewRecord : boolean
3455 hasForeignKeys : boolean
3556 hasImportContent : boolean
3657 dragHandleProps ?: any
3758 onUpdateColumn : ( changes : Partial < ColumnField > ) => void
3859 onRemoveColumn : ( ) => void
60+ onEditForeignKey : ( relation ?: ForeignKey ) => void
3961}
4062
4163const Column = ( {
4264 column = EMPTY_OBJ as ColumnField ,
65+ relations = EMPTY_ARR as ForeignKey [ ] ,
4366 enumTypes = EMPTY_ARR as EnumeratedType [ ] ,
4467 isNewRecord = false ,
4568 hasForeignKeys = false ,
4669 hasImportContent = false ,
4770 dragHandleProps = EMPTY_OBJ ,
48- onUpdateColumn = noop ,
49- onRemoveColumn = noop ,
71+ onUpdateColumn,
72+ onRemoveColumn,
73+ onEditForeignKey,
5074} : ColumnProps ) => {
75+ const { project } = useProjectContext ( )
76+ const [ open , setOpen ] = useState ( false )
5177 const suggestions : Suggestion [ ] = typeExpressionSuggestions ?. [ column . format ] ?? [ ]
5278
5379 const settingsCount = [
@@ -57,26 +83,142 @@ const Column = ({
5783 column . isArray ? 1 : 0 ,
5884 ] . reduce ( ( a , b ) => a + b , 0 )
5985
86+ const { data } = useForeignKeyConstraintsQuery ( {
87+ projectRef : project ?. ref ,
88+ connectionString : project ?. connectionString ,
89+ schema : column . schema ,
90+ } )
91+
92+ const getRelationStatus = ( fk : ForeignKey ) => {
93+ const existingRelation = ( data ?? [ ] ) . find ( ( x ) => x . id === fk . id )
94+ const stateRelation = relations . find ( ( x ) => x . id === fk . id )
95+
96+ if ( stateRelation ?. toRemove ) return 'REMOVE'
97+ if ( existingRelation === undefined && stateRelation !== undefined ) return 'ADD'
98+ if ( existingRelation !== undefined && stateRelation !== undefined ) {
99+ const hasUpdated = checkIfRelationChanged ( existingRelation , stateRelation )
100+ if ( hasUpdated ) return 'UPDATE'
101+ else return undefined
102+ }
103+ }
104+
105+ const hasChangesInRelations = relations
106+ . map ( ( r ) => getRelationStatus ( r ) )
107+ . some ( ( x ) => x !== undefined )
108+
60109 return (
61110 < div className = "flex w-full items-center" >
62111 < div className = { `w-[5%] ${ ! isNewRecord ? 'hidden' : '' } ` } >
63112 < div className = "cursor-drag" { ...dragHandleProps } >
64- < IconMenu strokeWidth = { 1 } size = { 15 } />
113+ < Menu strokeWidth = { 1 } size = { 16 } />
65114 </ div >
66115 </ div >
67116 < div className = "w-[25%]" >
68117 < div className = "flex w-[95%] items-center justify-between" >
69118 < Input
70- value = { column . name }
71119 size = "small"
120+ value = { column . name }
72121 title = { column . name }
73122 disabled = { hasImportContent }
74123 placeholder = "column_name"
75- className = { `table-editor-columns-input bg-surface-100 lg:gap-0 ${
124+ className = { cn (
125+ '[&>div>div>div>input]:py-1.5 [&>div>div>div>input]:border-r-transparent [&>div>div>div>input]:rounded-r-none' ,
76126 hasImportContent ? 'opacity-50' : ''
77- } rounded-md` }
127+ ) }
78128 onChange = { ( event : any ) => onUpdateColumn ( { name : event . target . value } ) }
79129 />
130+ { relations . filter ( ( r ) => ! r . toRemove ) . length === 0 ? (
131+ < Button
132+ type = "dashed"
133+ className = "rounded-l-none h-[30px] py-0 px-2"
134+ onClick = { ( ) => onEditForeignKey ( ) }
135+ >
136+ < Link size = { 12 } />
137+ </ Button >
138+ ) : (
139+ < Popover_Shadcn_ open = { open } onOpenChange = { setOpen } modal = { false } >
140+ < PopoverTrigger_Shadcn_ asChild >
141+ < Button type = "default" className = "rounded-l-none h-[30px] py-0 px-2" >
142+ < Link size = { 12 } />
143+ </ Button >
144+ </ PopoverTrigger_Shadcn_ >
145+ < PopoverContent_Shadcn_
146+ className = { cn ( 'p-0' , hasChangesInRelations ? 'w-96' : 'w-72' ) }
147+ side = "bottom"
148+ align = "end"
149+ >
150+ < div className = "text-xs px-2 pt-2" >
151+ Involved in { relations . length } foreign key{ relations . length > 1 ? 's' : '' }
152+ </ div >
153+ < Command_Shadcn_ >
154+ < CommandList_Shadcn_ >
155+ < CommandGroup_Shadcn_ >
156+ { relations . map ( ( relation , idx ) => {
157+ const key = String ( relation ?. id ?? `${ column . id } -relation-${ idx } ` )
158+ const status = getRelationStatus ( relation )
159+ if ( status === 'REMOVE' ) return null
160+
161+ return (
162+ < CommandItem_Shadcn_
163+ key = { key }
164+ value = { key }
165+ className = "cursor-pointer w-full"
166+ onSelect = { ( ) => onEditForeignKey ( relation ) }
167+ onClick = { ( ) => onEditForeignKey ( relation ) }
168+ >
169+ { status === undefined ? (
170+ < div className = "w-full flex items-center justify-between truncate" >
171+ { relation . name }
172+ </ div >
173+ ) : (
174+ < div className = "flex items-center gap-x-2 truncate" >
175+ < Badge variant = { status === 'ADD' ? 'brand' : 'warning' } >
176+ { status }
177+ </ Badge >
178+ < p className = "truncate" >
179+ { relation . name || (
180+ < >
181+ To{ ' ' }
182+ { relation . columns
183+ . filter ( ( c ) => c . source === column . name )
184+ . map ( ( c ) => {
185+ return (
186+ < code key = { `${ c . source } -${ c . target } ` } >
187+ { relation . schema } .{ relation . table } .{ c . target }
188+ </ code >
189+ )
190+ } ) }
191+ { relation . columns . length > 1 && (
192+ < >
193+ and { relation . columns . length - 1 } other column
194+ { relation . columns . length > 2 ? 's' : '' }
195+ </ >
196+ ) }
197+ </ >
198+ ) }
199+ </ p >
200+ </ div >
201+ ) }
202+ </ CommandItem_Shadcn_ >
203+ )
204+ } ) }
205+ </ CommandGroup_Shadcn_ >
206+ < CommandSeparator_Shadcn_ />
207+ < CommandGroup_Shadcn_ >
208+ < CommandItem_Shadcn_
209+ className = "cursor-pointer w-full gap-x-2"
210+ onSelect = { ( ) => onEditForeignKey ( ) }
211+ onClick = { ( ) => onEditForeignKey ( ) }
212+ >
213+ < Plus size = { 14 } strokeWidth = { 1.5 } />
214+ < p > Add foreign key relation</ p >
215+ </ CommandItem_Shadcn_ >
216+ </ CommandGroup_Shadcn_ >
217+ </ CommandList_Shadcn_ >
218+ </ Command_Shadcn_ >
219+ </ PopoverContent_Shadcn_ >
220+ </ Popover_Shadcn_ >
221+ ) }
80222 </ div >
81223 </ div >
82224 < div className = "w-[25%]" >
@@ -200,12 +342,12 @@ const Column = ({
200342 >
201343 < div className = "group flex items-center -space-x-1" >
202344 { settingsCount > 0 && (
203- < div className = "rounded-full bg-foreground py-0.5 px-2 text-xs text-background" >
345+ < div className = "rounded-full bg-foreground h-4 w-4 flex items-center justify-center text-xs text-background" >
204346 { settingsCount }
205347 </ div >
206348 ) }
207349 < div className = "text-foreground-light transition-colors group-hover:text-foreground" >
208- < IconSettings size = { 18 } strokeWidth = { 1 } />
350+ < Settings size = { 16 } strokeWidth = { 1 } />
209351 </ div >
210352 </ div >
211353 </ Popover >
@@ -215,7 +357,7 @@ const Column = ({
215357 { ! hasImportContent && (
216358 < div className = "flex w-[5%] justify-end" >
217359 < button className = "cursor-pointer" onClick = { ( ) => onRemoveColumn ( ) } >
218- < IconX strokeWidth = { 1 } />
360+ < X size = { 16 } strokeWidth = { 1 } />
219361 </ button >
220362 </ div >
221363 ) }
0 commit comments