agent-ecosystem/src/renderer/components/team/members/MemberDetailHeader.tsx
iliya af5f5af18b feat: implement member role update functionality
- Added IPC channel for updating a team member's role, including validation for team and member names.
- Implemented `handleUpdateMemberRole` function to manage role changes and notify the team lead of updates.
- Updated `TeamDataService` to support role changes and ensure data consistency.
- Enhanced UI components to allow role editing and display loading states during updates.
- Integrated role update handling in the team management dialogs, improving user experience.
2026-02-24 20:29:41 +02:00

95 lines
3.2 KiB
TypeScript

import { useState } from 'react';
import { Badge } from '@renderer/components/ui/badge';
import { DialogDescription, DialogTitle } from '@renderer/components/ui/dialog';
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { agentAvatarUrl, getMemberDotClass, getPresenceLabel } from '@renderer/utils/memberHelpers';
import { Pencil } from 'lucide-react';
import { MemberRoleEditor } from './MemberRoleEditor';
import type { ResolvedTeamMember } from '@shared/types';
interface MemberDetailHeaderProps {
member: ResolvedTeamMember;
isTeamAlive?: boolean;
isTeamProvisioning?: boolean;
onUpdateRole?: (newRole: string | undefined) => Promise<void> | void;
updatingRole?: boolean;
}
export const MemberDetailHeader = ({
member,
isTeamAlive,
isTeamProvisioning,
onUpdateRole,
updatingRole,
}: MemberDetailHeaderProps): React.JSX.Element => {
const [editing, setEditing] = useState(false);
const role = member.role || formatAgentRole(member.agentType);
const presenceLabel = getPresenceLabel(member, isTeamAlive, isTeamProvisioning);
const dotClass = getMemberDotClass(member, isTeamAlive, isTeamProvisioning);
const canEditRole =
member.agentType !== 'team-lead' && !member.removedAt && !isTeamProvisioning && !!onUpdateRole;
return (
<div className="flex items-center gap-3">
<div className="relative shrink-0">
<img
src={agentAvatarUrl(member.name, 96)}
alt={member.name}
className="size-12 rounded-full bg-[var(--color-surface-raised)]"
loading="lazy"
/>
<span
className={`absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`}
aria-label={presenceLabel}
/>
</div>
<div className="min-w-0 flex-1">
<DialogTitle className="truncate">{member.name}</DialogTitle>
<DialogDescription className="mt-1 flex items-center gap-2">
{editing ? (
<MemberRoleEditor
currentRole={member.role}
saving={updatingRole}
onSave={async (newRole) => {
try {
await onUpdateRole?.(newRole);
setEditing(false);
} catch {
// stay in editing mode so user can retry
}
}}
onCancel={() => setEditing(false)}
/>
) : (
<>
<span>{role || 'No role'}</span>
{canEditRole && (
<button
type="button"
className="inline-flex items-center text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text-secondary)]"
onClick={() => setEditing(true)}
aria-label="Edit role"
>
<Pencil size={12} />
</button>
)}
</>
)}
{!editing && (
<Badge
variant="secondary"
className="px-1.5 py-0.5 text-[10px] font-normal leading-none text-[var(--color-text-muted)]"
>
{presenceLabel}
</Badge>
)}
</DialogDescription>
</div>
</div>
);
};