@@ -12,13 +12,13 @@ import { useListGraphRelationsQuery } from '@app-builder/queries/graph/list-rela
1212import { createGraphRelationPayloadSchema } from '@app-builder/schemas/graph' ;
1313import { getFieldErrors , handleSubmit } from '@app-builder/utils/form' ;
1414import { useForm } from '@tanstack/react-form' ;
15+ import { createColumnHelper , getCoreRowModel } from '@tanstack/react-table' ;
1516import { useMemo , useState } from 'react' ;
1617import { useTranslation } from 'react-i18next' ;
17- import { Button , Input , Modal } from 'ui-design-system' ;
18+ import { Button , Input , Modal , Table , useTable } from 'ui-design-system' ;
1819import { Icon } from 'ui-icons' ;
1920import { z } from 'zod/v4' ;
2021import { GraphOptionSelect } from './GraphOptionSelect' ;
21- import { useGraphSession } from './GraphSessionContext' ;
2222import { GraphTabSwitch , tabSwitchOptions } from './GraphTabSwitch' ;
2323import { graphI18n } from './graph-i18n' ;
2424
@@ -64,6 +64,8 @@ function joinableFields(rightTable: TableModel | undefined, leftField: DataModel
6464/** The relations sharing one label, which is what the settings UI calls a "setting". */
6565type RelationGroup = { label : string ; relations : GraphRelation [ ] } ;
6666
67+ const relationGroupColumnHelper = createColumnHelper < RelationGroup > ( ) ;
68+
6769function groupRelationsByLabel ( relations : GraphRelation [ ] ) : RelationGroup [ ] {
6870 const groups = new Map < string , GraphRelation [ ] > ( ) ;
6971 for ( const relation of relations ) {
@@ -158,7 +160,7 @@ function TableFieldSelect({
158160
159161function RelationEndpoints ( { relation } : { relation : GraphRelation } ) {
160162 return (
161- < div className = "text-grey-secondary flex min-w-0 items-center gap-xs text-xs " >
163+ < div className = "text-grey-secondary flex min-w-0 items-center gap-xs text-sm " >
162164 < span className = "truncate" >
163165 { relation . leftType } .{ relation . leftField }
164166 </ span >
@@ -302,7 +304,6 @@ function RelationSettingPanel({
302304 relations : GraphRelation [ ] ;
303305} ) {
304306 const { t } = useTranslation ( graphI18n ) ;
305- const { reloadGraph } = useGraphSession ( ) ;
306307 const createMutation = useCreateGraphRelationMutation ( ) ;
307308 const deleteMutation = useDeleteGraphRelationMutation ( ) ;
308309 const [ scope , setScope ] = useState < RelationScope > ( 'same-table' ) ;
@@ -316,7 +317,6 @@ function RelationSettingPanel({
316317 { ...value , label } ,
317318 {
318319 onSuccess : ( ) => {
319- reloadGraph ( ) ;
320320 formApi . setFieldValue ( 'leftField' , '' ) ;
321321 formApi . setFieldValue ( 'rightField' , '' ) ;
322322 } ,
@@ -452,7 +452,7 @@ function RelationSettingPanel({
452452 mode = "icon"
453453 aria-label = { t ( 'graph:settings.delete_relation' ) }
454454 disabled = { deleteMutation . isPending }
455- onClick = { ( ) => deleteMutation . mutate ( { relationId : relation . id } , { onSuccess : reloadGraph } ) }
455+ onClick = { ( ) => deleteMutation . mutate ( { relationId : relation . id } ) }
456456 >
457457 < Icon icon = "delete" className = "size-4" />
458458 </ Button >
@@ -474,7 +474,6 @@ function RelationSettingPanel({
474474export function GraphRelationsSettings ( { dataModel } : { dataModel : DataModel } ) {
475475 const { t } = useTranslation ( graphI18n ) ;
476476 const relationsQuery = useListGraphRelationsQuery ( ) ;
477- const { reloadGraph } = useGraphSession ( ) ;
478477 const deleteRelationsMutation = useDeleteGraphRelationsMutation ( ) ;
479478
480479 const [ createModalOpen , setCreateModalOpen ] = useState ( false ) ;
@@ -491,13 +490,86 @@ export function GraphRelationsSettings({ dataModel }: { dataModel: DataModel })
491490 return relationsQuery . data . filter ( ( relation ) => relation . label === panelLabel ) ;
492491 } , [ panelLabel , relationsQuery . data ] ) ;
493492
493+ const columns = useMemo (
494+ ( ) => [
495+ relationGroupColumnHelper . accessor ( ( row ) => row . label , {
496+ id : 'label' ,
497+ header : t ( 'graph:settings.create_label.field' ) ,
498+ size : 200 ,
499+ } ) ,
500+ relationGroupColumnHelper . accessor ( ( row ) => row . relations , {
501+ id : 'relations' ,
502+ header : t ( 'graph:settings.relations' ) ,
503+ size : 400 ,
504+ cell : ( { getValue } ) => {
505+ const relations = getValue ( ) ;
506+ if ( relations . length === 0 ) return null ;
507+
508+ return (
509+ < ul className = "flex flex-col gap-xs py-xs" >
510+ { relations . map ( ( relation ) => (
511+ < li key = { relation . id } >
512+ < RelationEndpoints relation = { relation } />
513+ </ li >
514+ ) ) }
515+ </ ul >
516+ ) ;
517+ } ,
518+ } ) ,
519+ relationGroupColumnHelper . display ( {
520+ id : 'actions' ,
521+ size : 80 ,
522+ cell : ( { cell } ) => {
523+ const group = cell . row . original ;
524+ const buttonClass = 'group-hover:visible invisible' ;
525+ return (
526+ < div className = "flex gap-sm" >
527+ < div className = { buttonClass } >
528+ < Button
529+ type = "button"
530+ variant = "secondary"
531+ appearance = "stroked"
532+ mode = "icon"
533+ aria-label = { t ( 'common:edit' ) }
534+ onClick = { ( ) => setPanelLabel ( group . label ) }
535+ >
536+ < Icon icon = "edit-square" className = "size-6 shrink-0" />
537+ </ Button >
538+ </ div >
539+ < div className = { buttonClass } >
540+ < Button
541+ type = "button"
542+ variant = "secondary"
543+ appearance = "stroked"
544+ mode = "icon"
545+ aria-label = { t ( 'graph:settings.delete_group' , { label : group . label } ) }
546+ onClick = { ( ) => setDeleteTarget ( group ) }
547+ >
548+ < Icon icon = "delete" className = "size-6 shrink-0" />
549+ </ Button >
550+ </ div >
551+ </ div >
552+ ) ;
553+ } ,
554+ } ) ,
555+ ] ,
556+ [ t ] ,
557+ ) ;
558+
559+ const { table, getBodyProps, rows, getContainerProps } = useTable ( {
560+ data : groups ,
561+ columns,
562+ columnResizeMode : 'onChange' ,
563+ getCoreRowModel : getCoreRowModel ( ) ,
564+ enableSorting : false ,
565+ } ) ;
566+
494567 // On failure the modal stays open, so the user can retry what is left of the setting.
495568 const onDeleteSetting = ( group : RelationGroup ) => {
496569 deleteRelationsMutation . mutate (
497570 group . relations . map ( ( relation ) => relation . id ) ,
498571 {
499572 onSuccess : ( ) => {
500- reloadGraph ( ) ;
501573 if ( panelLabel === group . label ) setPanelLabel ( null ) ;
502574 setDeleteTarget ( null ) ;
503575 } ,
@@ -506,66 +578,42 @@ export function GraphRelationsSettings({ dataModel }: { dataModel: DataModel })
506578 } ;
507579
508580 return (
509- < div className = "flex min-h-0 flex-1 flex-col gap-lg overflow-y-auto" >
510- < section className = "flex flex-col gap-md" >
511- < div className = "flex items-center justify-between gap-md" >
512- < h2 className = "text-grey-primary text-sm font-semibold" > { t ( 'graph:settings.configured_relations' ) } </ h2 >
513- < Button variant = "primary" onClick = { ( ) => setCreateModalOpen ( true ) } >
514- < Icon icon = "plus" className = "size-4" />
581+ < >
582+ < CollapsiblePaper . Container >
583+ < CollapsiblePaper . Title >
584+ < span className = "flex-1" > { t ( 'graph:settings.configured_relations' ) } </ span >
585+ < Button
586+ onClick = { ( event ) => {
587+ event . stopPropagation ( ) ;
588+ setCreateModalOpen ( true ) ;
589+ } }
590+ >
591+ < Icon icon = "plus" className = "size-5" />
515592 { t ( 'common:create' ) }
516593 </ Button >
517- </ div >
518-
519- { relationsQuery . isPending ? (
520- < p className = "text-grey-secondary text-sm" > { t ( 'graph:settings.loading' ) } </ p >
521- ) : relationsQuery . isError ? (
522- < div className = "flex items-center gap-sm" >
523- < p className = "text-grey-secondary text-sm" > { t ( 'graph:settings.load_error' ) } </ p >
524- < Button variant = "secondary" onClick = { ( ) => relationsQuery . refetch ( ) } >
525- { t ( 'common:retry' ) }
526- </ Button >
527- </ div >
528- ) : groups . length === 0 ? (
529- < p className = "text-grey-secondary text-sm" > { t ( 'graph:settings.empty' ) } </ p >
530- ) : (
531- < div className = "flex flex-col gap-md" >
532- { groups . map ( ( group ) => (
533- < CollapsiblePaper . Container key = { group . label } defaultOpen = { false } >
534- < CollapsiblePaper . Title size = "small" iconPosition = "left" >
535- < span className = "text-grey-primary min-w-0 flex-1 truncate text-sm font-medium" > { group . label } </ span >
536- < div
537- className = "flex shrink-0 items-center gap-sm"
538- onClick = { ( event ) => event . stopPropagation ( ) }
539- onKeyDown = { ( event ) => event . stopPropagation ( ) }
540- >
541- < Button variant = "secondary" appearance = "stroked" onClick = { ( ) => setPanelLabel ( group . label ) } >
542- { t ( 'common:edit' ) }
543- </ Button >
544- < Button
545- variant = "destructive"
546- appearance = "stroked"
547- mode = "icon"
548- aria-label = { t ( 'graph:settings.delete_group' , { label : group . label } ) }
549- onClick = { ( ) => setDeleteTarget ( group ) }
550- >
551- < Icon icon = "delete" className = "size-4" />
552- </ Button >
553- </ div >
554- </ CollapsiblePaper . Title >
555- < CollapsiblePaper . Content >
556- < ul className = "divide-grey-border flex flex-col divide-y" >
557- { group . relations . map ( ( relation ) => (
558- < li key = { relation . id } className = "py-sm first:pt-0 last:pb-0" >
559- < RelationEndpoints relation = { relation } />
560- </ li >
561- ) ) }
562- </ ul >
563- </ CollapsiblePaper . Content >
564- </ CollapsiblePaper . Container >
565- ) ) }
566- </ div >
567- ) }
568- </ section >
594+ </ CollapsiblePaper . Title >
595+ < CollapsiblePaper . Content >
596+ { relationsQuery . isPending ? (
597+ < p className = "text-grey-secondary text-sm" > { t ( 'graph:settings.loading' ) } </ p >
598+ ) : relationsQuery . isError ? (
599+ < div className = "flex items-center gap-sm" >
600+ < p className = "text-grey-secondary text-sm" > { t ( 'graph:settings.load_error' ) } </ p >
601+ < Button variant = "secondary" onClick = { ( ) => relationsQuery . refetch ( ) } >
602+ { t ( 'common:retry' ) }
603+ </ Button >
604+ </ div >
605+ ) : (
606+ < Table . Container { ...getContainerProps ( ) } className = "max-h-96" >
607+ < Table . Header headerGroups = { table . getHeaderGroups ( ) } />
608+ < Table . Body { ...getBodyProps ( ) } >
609+ { rows . map ( ( row ) => (
610+ < Table . Row key = { row . id } className = "hover:bg-surface-row-hover group" row = { row } />
611+ ) ) }
612+ </ Table . Body >
613+ </ Table . Container >
614+ ) }
615+ </ CollapsiblePaper . Content >
616+ </ CollapsiblePaper . Container >
569617
570618 < CreateLabelModal open = { createModalOpen } onOpenChange = { setCreateModalOpen } onSubmitLabel = { setPanelLabel } />
571619
@@ -592,6 +640,6 @@ export function GraphRelationsSettings({ dataModel }: { dataModel: DataModel })
592640 relations = { panelRelations }
593641 />
594642 ) : null }
595- </ div >
643+ </ >
596644 ) ;
597645}
0 commit comments