agent-ecosystem/src/renderer/components/team/TaskTooltip.tsx
iliya 86a1abdefa feat: introduce 'needsFix' review state and enhance task management features
- Added 'needsFix' as a new review state to improve task tracking and management.
- Updated kanban and task handling functions to accommodate the new review state, including modifications to clearKanban and task status updates.
- Enhanced task briefing output to include sections for tasks needing fixes, pending tasks, and approved tasks, improving clarity in task management.
- Updated UI components to display the 'needsFix' state appropriately across various views, including task rows and tooltips.
- Refactored tests to cover new functionalities related to the 'needsFix' state, ensuring comprehensive coverage of task management scenarios.
2026-03-09 13:50:45 +02:00

128 lines
4.6 KiB
TypeScript

import { useMemo } from 'react';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { REVIEW_STATE_DISPLAY, buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { getTaskKanbanColumn } from '@shared/utils/reviewState';
import { formatTaskDisplayLabel, taskMatchesRef } from '@shared/utils/taskIdentity';
import type { TeamTaskWithKanban } from '@shared/types';
/**
* Status/kanban-column display colors.
* Matches the kanban column palette from KanbanBoard.tsx.
*/
const STATUS_COLORS: Record<string, { text: string; bg: string }> = {
pending: { text: '#60a5fa', bg: 'rgba(59, 130, 246, 0.15)' }, // blue
todo: { text: '#60a5fa', bg: 'rgba(59, 130, 246, 0.15)' },
in_progress: { text: '#facc15', bg: 'rgba(234, 179, 8, 0.15)' }, // yellow
completed: { text: '#4ade80', bg: 'rgba(34, 197, 94, 0.15)' }, // green
done: { text: '#4ade80', bg: 'rgba(34, 197, 94, 0.15)' },
review: { text: '#a78bfa', bg: 'rgba(139, 92, 246, 0.15)' }, // purple
approved: { text: '#34d399', bg: 'rgba(34, 197, 94, 0.25)' }, // bright green
deleted: { text: '#f87171', bg: 'rgba(239, 68, 68, 0.15)' }, // red
};
function getEffectiveColumn(task: TeamTaskWithKanban): string {
const reviewColumn = getTaskKanbanColumn(task);
if (reviewColumn) return reviewColumn;
if (task.status === 'pending') return 'todo';
if (task.status === 'completed') return 'done';
return task.status;
}
function getStatusLabel(column: string): string {
const labels: Record<string, string> = {
todo: 'To Do',
pending: 'To Do',
in_progress: 'In Progress',
done: 'Done',
completed: 'Done',
review: 'Review',
approved: 'Approved',
deleted: 'Deleted',
};
return labels[column] ?? column;
}
interface TaskTooltipProps {
/** Canonical task id or short display id reference. */
taskId: string;
/** Rendered trigger element. */
children: React.ReactElement;
/** Tooltip placement. */
side?: 'top' | 'bottom' | 'left' | 'right';
}
/**
* Tooltip that shows task summary on hover over any #taskId link.
* Reads task data from the current team in the store.
*/
export const TaskTooltip = ({
taskId,
children,
side = 'top',
}: TaskTooltipProps): React.JSX.Element => {
const tasks = useStore((s) => s.selectedTeamData?.tasks);
const members = useStore((s) => s.selectedTeamData?.members);
const task = useMemo(() => tasks?.find((t) => taskMatchesRef(t, taskId)), [tasks, taskId]);
const colorMap = useMemo(
() => (members ? buildMemberColorMap(members) : new Map<string, string>()),
[members]
);
// If task not found, render children without tooltip
if (!task) return children;
const column = getEffectiveColumn(task);
const statusColor = STATUS_COLORS[column] ?? STATUS_COLORS.pending;
const label = getStatusLabel(column);
return (
<Tooltip>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent side={side} className="max-w-xs space-y-1.5 p-2.5">
{/* Subject */}
<div className="text-xs font-medium text-[var(--color-text)]">
<span className="text-[var(--color-text-muted)]">{formatTaskDisplayLabel(task)}</span>{' '}
{task.subject}
</div>
{/* Status badge */}
<div className="flex items-center gap-2">
<span
className="inline-block rounded px-1.5 py-0.5 text-[10px] font-medium"
style={{ color: statusColor.text, backgroundColor: statusColor.bg }}
>
{label}
</span>
{task.reviewState === 'needsFix' ? (
<span
className={`inline-block rounded px-1.5 py-0.5 text-[10px] font-medium ${REVIEW_STATE_DISPLAY.needsFix.bg} ${REVIEW_STATE_DISPLAY.needsFix.text}`}
>
{REVIEW_STATE_DISPLAY.needsFix.label}
</span>
) : null}
{/* Owner */}
{task.owner ? (
<MemberBadge name={task.owner} color={colorMap.get(task.owner)} />
) : (
<span className="text-[10px] text-[var(--color-text-muted)]">Unassigned</span>
)}
</div>
{/* Description — full markdown with scroll */}
{task.description ? (
<div className="max-h-[200px] overflow-y-auto text-[10px]">
<MarkdownViewer content={task.description} maxHeight="max-h-none" bare />
</div>
) : null}
</TooltipContent>
</Tooltip>
);
};