import { Draggable } from '@hello-pangea/dnd' import type { AgentInfo } from '../types/visual' import { PRIORITY_META, AGENT_STATUS_LABEL, type KanbanTask } from '../types/kanban' import { getWorkItemRoleLabel, humanizeWorkItemRoleId } from '../lib/workItemIdentity' import { getLinkedRuntimeTaskId } from '../lib/workItemRuntimeIds' import { useI18n } from '../i18n' const STATUS_BADGE: Record = { todo: { label: 'To do', color: '#9ca3af' }, in_progress: { label: 'In progress', color: '#f59e0b' }, in_review: { label: 'In review', color: '#fbbf24' }, done: { label: 'Done', color: '#34d399' }, running: { label: 'Running', color: '#34d399' }, idle: { label: 'Idle', color: '#6366f1' }, blocked: { label: 'Blocked', color: '#f97316' }, awaiting_peer: { label: 'Awaiting', color: '#fbbf24' }, awaiting_manager_review: { label: 'Mgr Review', color: '#fbbf24' }, awaiting_human: { label: 'Human Review', color: '#fbbf24' }, awaiting_review: { label: 'In Review', color: '#fbbf24' }, failed: { label: 'Failed', color: '#ef4444' }, cancelled: { label: 'Cancelled', color: '#9ca3af' }, } interface KanbanCardProps { task: KanbanTask index: number agents: AgentInfo[] officeMap?: Record companyMode?: boolean isSelected?: boolean onClick: (task: KanbanTask) => void onStart?: (taskId: string) => void } export function KanbanCard({ task, index, agents, officeMap, companyMode, isSelected, onClick, onStart }: KanbanCardProps) { const { t, translateMaybe } = useI18n() const assignees = task.assigneeIds .map(id => agents.find(a => a.agent_id === id)) .filter(Boolean) as AgentInfo[] const priority = task.priority ? PRIORITY_META[task.priority] : null const crossOffice = officeMap && assignees.length > 1 && new Set(assignees.map(a => officeMap[a.agent_id]).filter(Boolean)).size > 1 const runtimeActive = task.agentStatus && task.agentStatus !== 'idle' const depCount = task.dependencies?.length ?? 0 // Hide status badge when runtime bar is showing (avoids "Running" + "Thinking..." redundancy) // Also hide for 'pending' (default state, no badge needed in todo column) const phaseBadge = task.phase const statusBadge = (!runtimeActive && phaseBadge && phaseBadge !== 'ready') ? STATUS_BADGE[phaseBadge] ?? null : null const employee = task.employeeAssignment const roleLabel = getWorkItemRoleLabel(task) const gate = task.workItemGate const managerLabel = humanizeWorkItemRoleId(task.managerRoleId) const blockerLabel = (task.blockedReason ?? '').trim() const reworkLabel = (task.reworkFeedback ?? '').trim() const linkedRuntimeTaskId = getLinkedRuntimeTaskId(task) const runtimeStatusLabel = task.agentStatus ? (translateMaybe('agent.status', task.agentStatus) || AGENT_STATUS_LABEL[task.agentStatus] || task.agentStatus) : '' return ( {(provided, snapshot) => (
{ if (e.button === 0 && !snapshot.isDragging) onClick(task) }} >
{task.displayId} {statusBadge && ( {translateMaybe('kanban.status', phaseBadge) || statusBadge.label} )} {depCount > 0 && ( {t('kanban.dep', { count: depCount })} )} {crossOffice && } {onStart && ( )}

{task.title}

{(roleLabel || employee?.name || gate?.type || task.originChannel || task.workItemProjectionId) && (
{roleLabel && ( {roleLabel} )} {employee?.name && ( 👤 {employee.name} )} {task.workItemProjectionId && ( {task.workItemProjectionId} )} {gate?.type && ( {gate.type === 'review' ? '\u2709' : gate.type === 'approval' ? '\u2713' : '\u270B'} {gate.type} )} {task.originChannel && ( #{task.originChannel} )} {managerLabel && ( {managerLabel} )}
)} {(blockerLabel || reworkLabel || linkedRuntimeTaskId) && (
{blockerLabel && {blockerLabel}} {reworkLabel && {reworkLabel}} {linkedRuntimeTaskId && ( {t('common.runtime')} )}
)} {runtimeActive && (
{task.agentStatus === 'tool_active' && task.currentTool ? task.currentTool : runtimeStatusLabel}
)} {task.tags.length > 0 && (
{task.tags.slice(0, 3).map(tag => ( {tag} ))}
)} {(priority || assignees.length > 0) && (
{priority && ( {priority.symbol} )}
{assignees.slice(0, 3).map(a => ( {a.name.charAt(0).toUpperCase()} ))} {assignees.length > 3 && ( +{assignees.length - 3} )}
)}
)}
) }