/** * TeamGraphTab — wraps GraphView for use as a dedicated tab. * Provides Fullscreen button that opens the overlay. */ import { lazy, Suspense, useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { GraphView } from '@claude-teams/agent-graph'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { GraphActivityHud } from './GraphActivityHud'; import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover'; import { GraphNodePopover } from './GraphNodePopover'; import { GraphProvisioningHud } from './GraphProvisioningHud'; import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph'; import type { MemberActivityFilter, MemberDetailTab, } from '@renderer/components/team/members/memberDetailTypes'; const TeamGraphOverlay = lazy(() => import('./TeamGraphOverlay').then((m) => ({ default: m.TeamGraphOverlay })) ); export interface TeamGraphTabProps { teamName: string; isActive?: boolean; isPaneFocused?: boolean; } interface OpenProfileOptions { initialTab?: MemberDetailTab; initialActivityFilter?: MemberActivityFilter; } export const TeamGraphTab = ({ teamName, isActive = true, isPaneFocused = false, }: TeamGraphTabProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); const { openTeamPage, resetOwnerSlotAssignmentsToDefaults, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); const [fullscreen, setFullscreen] = useState(false); const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); // Typed event dispatchers (DRY — used in both events + renderOverlay) const dispatchOpenTask = useCallback( (taskId: string) => window.dispatchEvent(new CustomEvent('graph:open-task', { detail: { teamName, taskId } })), [teamName] ); const dispatchSendMessage = useCallback( (memberName: string) => window.dispatchEvent( new CustomEvent('graph:send-message', { detail: { teamName, memberName } }) ), [teamName] ); const dispatchOpenProfile = useCallback( (memberName: string, options?: OpenProfileOptions) => window.dispatchEvent( new CustomEvent('graph:open-profile', { detail: { teamName, memberName, ...options }, }) ), [teamName] ); const openCreateTask = useCallback(() => { openCreateTaskDialog(''); }, [openCreateTaskDialog]); useLayoutEffect(() => { if (!isActive) { return; } resetOwnerSlotAssignmentsToDefaults(); }, [isActive, resetOwnerSlotAssignmentsToDefaults]); // Task action dispatchers const dispatchTaskAction = useCallback( (action: string) => (taskId: string) => window.dispatchEvent(new CustomEvent(`graph:${action}`, { detail: { teamName, taskId } })), [teamName] ); const dispatchStartTask = useMemo(() => dispatchTaskAction('start-task'), [dispatchTaskAction]); const dispatchCompleteTask = useMemo( () => dispatchTaskAction('complete-task'), [dispatchTaskAction] ); const dispatchApproveTask = useMemo( () => dispatchTaskAction('approve-task'), [dispatchTaskAction] ); const dispatchRequestReview = useMemo( () => dispatchTaskAction('request-review'), [dispatchTaskAction] ); const dispatchRequestChanges = useMemo( () => dispatchTaskAction('request-changes'), [dispatchTaskAction] ); const dispatchCancelTask = useMemo(() => dispatchTaskAction('cancel-task'), [dispatchTaskAction]); const dispatchMoveBackToDone = useMemo( () => dispatchTaskAction('move-back-to-done'), [dispatchTaskAction] ); const dispatchDeleteTask = useMemo(() => dispatchTaskAction('delete-task'), [dispatchTaskAction]); const events: GraphEventPort = { onNodeDoubleClick: useCallback( (ref: GraphDomainRef) => { if (ref.kind === 'task') dispatchOpenTask(ref.taskId); else if (ref.kind === 'member') dispatchOpenProfile(ref.memberName); }, [dispatchOpenTask, dispatchOpenProfile] ), onSendMessage: dispatchSendMessage, onOpenTaskDetail: dispatchOpenTask, onOpenMemberProfile: useCallback( (memberName: string) => { dispatchOpenProfile(memberName); }, [dispatchOpenProfile] ), }; return (
{sidebarVisible ? ( ) : null}
setFullscreen(true)} onOpenTeamPage={openTeamPage} onCreateTask={openCreateTask} onToggleSidebar={toggleSidebarVisible} isSidebarVisible={sidebarVisible} renderTopToolbarContent={() => ( )} onOwnerSlotDrop={commitOwnerSlotDrop} renderHud={(hudProps) => { const extraHudProps = hudProps as typeof hudProps & { getViewportSize?: () => { width: number; height: number }; getActivityWorldRect?: (ownerNodeId: string) => { left: number; top: number; right: number; bottom: number; width: number; height: number; } | null; getCameraZoom?: () => number; worldToScreen?: (x: number, y: number) => { x: number; y: number }; getNodeWorldPosition?: (nodeId: string) => { x: number; y: number } | null; }; const { getViewportSize, focusNodeIds } = extraHudProps; return ( <> ); }} renderEdgeOverlay={({ edge, sourceNode, targetNode, onClose, onSelectNode }) => ( )} renderOverlay={({ node, onClose }) => ( )} />
{createTaskDialog} {fullscreen && ( setFullscreen(false)} sidebarVisible={sidebarVisible} onToggleSidebar={toggleSidebarVisible} onSendMessage={dispatchSendMessage} onOpenTaskDetail={dispatchOpenTask} onOpenMemberProfile={dispatchOpenProfile} /> )}
); };