11import type { EntityType } from "@bibgraph/types" ;
22import { logger } from "@bibgraph/utils" ;
33import { ActionIcon , Affix , Badge , Box , Code , Group , Modal , Paper , SegmentedControl , Stack , Text , Title , Tooltip } from "@mantine/core" ;
4- import { IconBookmark , IconBookmarkFilled , IconBookmarkOff , IconCode , IconListCheck , IconMenu2 , IconX } from "@tabler/icons-react" ;
4+ import { notifications } from "@mantine/notifications" ;
5+ import { IconBookmark , IconBookmarkFilled , IconBookmarkOff , IconCode , IconGraph , IconListCheck , IconMenu2 , IconX } from "@tabler/icons-react" ;
56import React , { ReactNode , useState } from "react" ;
67
78import { BORDER_STYLE_GRAY_3 , ICON_SIZE } from "@/config/style-constants" ;
89import { useQueryBookmarking } from "@/hooks/use-query-bookmarking" ;
910import { useResponsiveDesign } from "@/hooks/use-sprinkles" ;
1011import { useThemeColors } from "@/hooks/use-theme-colors" ;
1112import { useUserInteractions } from "@/hooks/use-user-interactions" ;
13+ import { useGraphList } from "@/hooks/useGraphList" ;
1214
1315import { AddToListModal } from "../catalogue/AddToListModal" ;
1416import { EntityDataDisplay } from "../EntityDataDisplay" ;
@@ -68,6 +70,13 @@ export const EntityDetailLayout = ({
6870 // Mobile navigation state
6971 const [ mobileActionsOpen , setMobileActionsOpen ] = useState ( false ) ;
7072
73+ // Graph list hook for adding entities to the graph
74+ const graphList = useGraphList ( ) ;
75+ const [ isAddingToGraph , setIsAddingToGraph ] = useState ( false ) ;
76+
77+ // Check if entity is already in graph
78+ const isInGraph = graphList . nodes . some ( node => node . entityId === entityId ) ;
79+
7180 const handleBookmarkToggle = async ( ) => {
7281 try {
7382 await ( userInteractions . isBookmarked ? userInteractions . unbookmarkEntity ( ) : userInteractions . bookmarkEntity ( {
@@ -91,6 +100,42 @@ export const EntityDetailLayout = ({
91100 logger . error ( "ui" , "Failed to toggle query bookmark" , { error, entityType, entityId } ) ;
92101 }
93102 } ;
103+
104+ const handleAddToGraphToggle = async ( ) => {
105+ setIsAddingToGraph ( true ) ;
106+ try {
107+ if ( isInGraph ) {
108+ await graphList . removeNode ( entityId ) ;
109+ notifications . show ( {
110+ title : "Removed from Graph" ,
111+ message : `${ displayName } removed from graph` ,
112+ color : "gray" ,
113+ } ) ;
114+ } else {
115+ await graphList . addNode ( {
116+ entityId,
117+ entityType,
118+ label : displayName ,
119+ provenance : "user" ,
120+ } ) ;
121+ notifications . show ( {
122+ title : "Added to Graph" ,
123+ message : `${ displayName } added to graph for analysis` ,
124+ color : "green" ,
125+ } ) ;
126+ }
127+ } catch ( error ) {
128+ logger . error ( "ui" , "Failed to toggle graph list" , { error, entityType, entityId } ) ;
129+ notifications . show ( {
130+ title : "Error" ,
131+ message : isInGraph ? "Failed to remove from graph" : "Failed to add to graph" ,
132+ color : "red" ,
133+ } ) ;
134+ } finally {
135+ setIsAddingToGraph ( false ) ;
136+ }
137+ } ;
138+
94139 return (
95140 < Box
96141 p = { isMobile ( ) ? "sm" : "xl" }
@@ -155,6 +200,19 @@ export const EntityDetailLayout = ({
155200
156201 < Group gap = "sm" >
157202 { /* Desktop Action Buttons */ }
203+ < Tooltip label = { isInGraph ? "Remove from graph" : "Add to graph for analysis" } position = "bottom" >
204+ < ActionIcon
205+ size = "lg"
206+ variant = { isInGraph ? "filled" : "light" }
207+ color = "grape"
208+ onClick = { handleAddToGraphToggle }
209+ loading = { isAddingToGraph || graphList . loading }
210+ data-testid = "add-to-graph-button"
211+ >
212+ < IconGraph size = { ICON_SIZE . XL } />
213+ </ ActionIcon >
214+ </ Tooltip >
215+
158216 < Tooltip label = "Add to catalogue list" position = "bottom" >
159217 < ActionIcon
160218 size = "lg"
@@ -236,6 +294,20 @@ export const EntityDetailLayout = ({
236294 < Stack gap = "sm" >
237295 < Text size = "sm" fw = { 600 } c = "dimmed" > Actions</ Text >
238296 < Group gap = "sm" justify = "space-around" >
297+ < ActionIcon
298+ size = "lg"
299+ variant = { isInGraph ? "filled" : "light" }
300+ color = "grape"
301+ onClick = { ( ) => {
302+ handleAddToGraphToggle ( ) ;
303+ setMobileActionsOpen ( false ) ;
304+ } }
305+ loading = { isAddingToGraph || graphList . loading }
306+ data-testid = "mobile-add-to-graph-button"
307+ >
308+ < IconGraph size = { ICON_SIZE . LG } />
309+ </ ActionIcon >
310+
239311 < ActionIcon
240312 size = "lg"
241313 variant = "light"
@@ -403,6 +475,19 @@ export const EntityDetailLayout = ({
403475 } }
404476 >
405477 < Group justify = "space-around" gap = "xs" >
478+ < Tooltip label = { isInGraph ? "Remove from graph" : "Add to graph" } position = "top" >
479+ < ActionIcon
480+ size = "lg"
481+ variant = { isInGraph ? "filled" : "light" }
482+ color = "grape"
483+ onClick = { handleAddToGraphToggle }
484+ loading = { isAddingToGraph || graphList . loading }
485+ aria-label = { isInGraph ? "Remove from graph" : "Add to graph" }
486+ >
487+ < IconGraph size = { ICON_SIZE . LG } />
488+ </ ActionIcon >
489+ </ Tooltip >
490+
406491 < Tooltip label = "Add to list" position = "top" >
407492 < ActionIcon
408493 size = "lg"
0 commit comments