import { useEffect, useMemo, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { useMemberStats } from '@renderer/hooks/useMemberStats'; import { isLeadMember } from '@shared/utils/leadDetection'; import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react'; import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries'; import { MemberDetailHeader } from './MemberDetailHeader'; import { MemberDetailStats } from './MemberDetailStats'; import { MemberLogsTab } from './MemberLogsTab'; import { MemberMessagesTab } from './MemberMessagesTab'; import { MemberStatsTab } from './MemberStatsTab'; import { MemberTasksTab } from './MemberTasksTab'; import { type MemberActivityFilter, type MemberDetailTab } from './memberDetailTypes'; import type { InboxMessage, LeadActivityState, MemberSpawnStatusEntry, ResolvedTeamMember, TeamTaskWithKanban, } from '@shared/types'; interface MemberDetailDialogProps { open: boolean; member: ResolvedTeamMember | null; teamName: string; members: ResolvedTeamMember[]; tasks: TeamTaskWithKanban[]; messages: InboxMessage[]; initialTab?: MemberDetailTab; initialActivityFilter?: MemberActivityFilter; isTeamAlive?: boolean; isTeamProvisioning?: boolean; isLaunchSettling?: boolean; leadActivity?: LeadActivityState; spawnEntry?: MemberSpawnStatusEntry; onClose: () => void; onSendMessage: () => void; onAssignTask: () => void; onTaskClick: (task: TeamTaskWithKanban) => void; onRemoveMember?: () => void; onUpdateRole?: (memberName: string, role: string | undefined) => Promise | void; updatingRole?: boolean; onViewMemberChanges?: (memberName: string, filePath?: string) => void; } export const MemberDetailDialog = ({ open, member, teamName, members, tasks, messages, initialTab = 'tasks', initialActivityFilter = 'all', isTeamAlive, isTeamProvisioning, isLaunchSettling, leadActivity, spawnEntry, onClose, onSendMessage, onAssignTask, onTaskClick, onRemoveMember, onUpdateRole, updatingRole, onViewMemberChanges, }: MemberDetailDialogProps): React.JSX.Element | null => { const memberTasks = useMemo( () => (member ? tasks.filter((t) => t.owner === member.name) : []), [tasks, member] ); const seedMemberMessages = useMemo( () => (member ? messages.filter((m) => m.from === member.name || m.to === member.name) : []), [messages, member] ); const memberMessages = seedMemberMessages; const memberActivityCount = useMemo(() => { if (!member) { return 0; } const leadId = `lead:${teamName}`; const leadName = members.find((candidate) => isLeadMember(candidate))?.name ?? `${teamName}-lead`; const ownerNodeId = member.name === leadName ? leadId : `member:${teamName}:${member.name}`; const entries = buildInlineActivityEntries({ data: { members, tasks, messages: memberMessages, }, teamName, leadId, leadName, ownerNodeIds: new Set([leadId, ownerNodeId]), }); return (entries.get(ownerNodeId) ?? []).length; }, [member, memberMessages, members, tasks, teamName]); const inProgressTasks = useMemo( () => memberTasks.filter((t) => t.status === 'in_progress').length, [memberTasks] ); const completedTasks = useMemo( () => memberTasks.filter((t) => t.status === 'completed').length, [memberTasks] ); const [activeTab, setActiveTab] = useState(initialTab); useEffect(() => { if (!open || !member) { return; } setActiveTab(initialTab); }, [initialTab, member, open]); const { stats: memberStats, loading: statsLoading, error: statsError, } = useMemberStats(teamName, member?.name ?? null); const totalTokens = memberStats ? memberStats.inputTokens + memberStats.outputTokens : null; if (!member) return null; return ( !nextOpen && onClose()}>
onUpdateRole(member.name, newRole) : undefined } updatingRole={updatingRole} />
setActiveTab(v as MemberDetailTab)} className="min-w-0 overflow-hidden" > Tasks {memberTasks.length > 0 && ( {memberTasks.length} )} Activity {memberActivityCount > 0 && ( {memberActivityCount} )} Stats Logs onViewMemberChanges?.(member.name, filePath)} onShowAllFiles={() => onViewMemberChanges?.(member.name)} /> {member.removedAt ? ( Removed {new Date(member.removedAt).toLocaleDateString()} ) : ( <> {onRemoveMember && !isLeadMember(member) && ( )} )}
); };