fix(graph): Profile opens member dialog (not message) + Add Task button

- Profile button dispatches 'graph:open-profile' → MemberDetailDialog
  (was incorrectly mapped to 'graph:send-message')
- Double-click on member → opens profile (was: send message)
- New "Add Task" button on member popover → CreateTaskDialog with owner pre-filled
- New events: graph:open-profile, graph:create-task
- TeamDetailView listens for both new events
This commit is contained in:
iliya 2026-03-28 15:08:32 +02:00
parent 26fe42739b
commit add9df2006
3 changed files with 50 additions and 6 deletions

View file

@ -246,11 +246,26 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
setSendDialogDefaultChip(undefined);
setSendDialogOpen(true);
};
const onOpenProfile = (e: Event) => {
const { teamName: tn, memberName } = (e as CustomEvent).detail ?? {};
if (tn !== teamName || !data) return;
const member = data.members.find((m: { name: string }) => m.name === memberName);
if (member) setSelectedMember(member);
};
const onCreateTask = (e: Event) => {
const { teamName: tn, owner } = (e as CustomEvent).detail ?? {};
if (tn !== teamName) return;
openCreateTaskDialog('', '', owner ?? '');
};
window.addEventListener('graph:open-task', onOpenTask);
window.addEventListener('graph:send-message', onSendMsg);
window.addEventListener('graph:open-profile', onOpenProfile);
window.addEventListener('graph:create-task', onCreateTask);
return () => {
window.removeEventListener('graph:open-task', onOpenTask);
window.removeEventListener('graph:send-message', onSendMsg);
window.removeEventListener('graph:open-profile', onOpenProfile);
window.removeEventListener('graph:create-task', onCreateTask);
};
});

View file

@ -7,7 +7,7 @@
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { agentAvatarUrl } from '@renderer/utils/memberHelpers';
import { MessageSquare, ExternalLink, User } from 'lucide-react';
import { MessageSquare, ExternalLink, User, Plus } from 'lucide-react';
import type { GraphNode } from '@claude-teams/agent-graph';
@ -17,6 +17,7 @@ interface GraphNodePopoverProps {
onSendMessage?: (memberName: string) => void;
onOpenTaskDetail?: (taskId: string) => void;
onOpenMemberProfile?: (memberName: string) => void;
onCreateTask?: (owner: string) => void;
}
export function GraphNodePopover({
@ -25,6 +26,7 @@ export function GraphNodePopover({
onSendMessage,
onOpenTaskDetail,
onOpenMemberProfile,
onCreateTask,
}: GraphNodePopoverProps): React.JSX.Element {
if (node.kind === 'member' || node.kind === 'lead') {
return (
@ -33,6 +35,7 @@ export function GraphNodePopover({
onClose={onClose}
onSendMessage={onSendMessage}
onOpenProfile={onOpenMemberProfile}
onCreateTask={onCreateTask}
/>
);
}
@ -66,11 +69,13 @@ function MemberPopoverContent({
onClose,
onSendMessage,
onOpenProfile,
onCreateTask,
}: {
node: GraphNode;
onClose: () => void;
onSendMessage?: (name: string) => void;
onOpenProfile?: (name: string) => void;
onCreateTask?: (owner: string) => void;
}): React.JSX.Element {
const memberName = node.domainRef.kind === 'member' ? node.domainRef.memberName : 'team-lead';
const avatarSrc = node.avatarUrl ?? agentAvatarUrl(memberName, 64);
@ -167,7 +172,7 @@ function MemberPopoverContent({
)}
{/* Actions */}
<div className="mt-3 flex gap-1.5">
<div className="mt-3 flex flex-wrap gap-1.5">
<Button
variant="outline"
size="sm"
@ -190,6 +195,17 @@ function MemberPopoverContent({
>
<User size={12} /> Profile
</Button>
<Button
variant="outline"
size="sm"
className="h-7 gap-1 px-2 text-xs"
onClick={() => {
onCreateTask?.(memberName);
onClose();
}}
>
<Plus size={12} /> Task
</Button>
</div>
</div>
);

View file

@ -37,18 +37,30 @@ export const TeamGraphTab = ({ teamName }: TeamGraphTabProps): React.JSX.Element
),
[teamName]
);
const dispatchOpenProfile = useCallback(
(memberName: string) =>
window.dispatchEvent(
new CustomEvent('graph:open-profile', { detail: { teamName, memberName } })
),
[teamName]
);
const dispatchCreateTask = useCallback(
(owner: string) =>
window.dispatchEvent(new CustomEvent('graph:create-task', { detail: { teamName, owner } })),
[teamName]
);
const events: GraphEventPort = {
onNodeDoubleClick: useCallback(
(ref: GraphDomainRef) => {
if (ref.kind === 'task') dispatchOpenTask(ref.taskId);
else if (ref.kind === 'member') dispatchSendMessage(ref.memberName);
else if (ref.kind === 'member') dispatchOpenProfile(ref.memberName);
},
[dispatchOpenTask, dispatchSendMessage]
[dispatchOpenTask, dispatchOpenProfile]
),
onSendMessage: dispatchSendMessage,
onOpenTaskDetail: dispatchOpenTask,
onOpenMemberProfile: dispatchSendMessage,
onOpenMemberProfile: dispatchOpenProfile,
};
return (
@ -64,7 +76,8 @@ export const TeamGraphTab = ({ teamName }: TeamGraphTabProps): React.JSX.Element
onClose={onClose}
onSendMessage={dispatchSendMessage}
onOpenTaskDetail={dispatchOpenTask}
onOpenMemberProfile={dispatchSendMessage}
onOpenMemberProfile={dispatchOpenProfile}
onCreateTask={dispatchCreateTask}
/>
)}
/>