import { useEffect, useMemo, useState } from 'react' import { NavLink } from 'react-router-dom' import { listActionTasks } from '../api/tasks.js' import { actionPath } from '../utils/routes.js' import { buildTasksByParent, countOpenTasks, MAX_TASK_UI_DEPTH } from '../utils/taskTree.js' import { getActionExecutionMeta } from '../utils/executionGraph.js' import { ExecutionFlowBadge } from './ExecutionFlowBadge.jsx' function actionTasksHref(actionId) { return `${actionPath(actionId)}#tasks` } function PlanOutlineTaskTree({ tasksByParent, parentId, actionId, depth, expandedTaskIds, onToggleTask, activeActionId, }) { const nodes = (tasksByParent.get(parentId || '') || []).filter( (task) => task.status !== 'done' && task.status !== 'discarded', ) if (nodes.length === 0 || depth > MAX_TASK_UI_DEPTH) { return null } return ( ) } function PlanOutlineActionNode({ action, tasks, activeActionId, defaultExpanded, executionGraph }) { const [actionOpen, setActionOpen] = useState(defaultExpanded) const [expandedTaskIds, setExpandedTaskIds] = useState(() => new Set()) const tasksByParent = useMemo(() => buildTasksByParent(tasks || []), [tasks]) const openTaskCount = useMemo(() => countOpenTasks(tasks || []), [tasks]) const hasOpenTasks = openTaskCount > 0 useEffect(() => { setActionOpen(defaultExpanded) }, [defaultExpanded]) function toggleTask(taskId) { setExpandedTaskIds((prev) => { const next = new Set(prev) if (next.has(taskId)) next.delete(taskId) else next.add(taskId) return next }) } const execMeta = getActionExecutionMeta(executionGraph, action.id) return (
  • {hasOpenTasks && ( )} 'analysis-split__nav-link analysis-split__nav-link--nested' + (isActive || activeActionId === action.id ? ' analysis-split__nav-link--active' : '') } > {action.title} {openTaskCount > 0 && ( ({openTaskCount}) )}
    {hasOpenTasks && actionOpen && ( )}
  • ) } export function PlanOutlineWorkTree({ openActions, activeActionId, enabled, executionGraph = null }) { const [tasksByAction, setTasksByAction] = useState({}) const actionIdsKey = useMemo( () => openActions.map((action) => action.id).join(','), [openActions], ) useEffect(() => { if (!enabled || openActions.length === 0) { setTasksByAction({}) return undefined } let cancelled = false Promise.all( openActions.map((action) => listActionTasks(action.id) .then((tasks) => [action.id, Array.isArray(tasks) ? tasks : []]) .catch(() => [action.id, []]), ), ).then((entries) => { if (!cancelled) setTasksByAction(Object.fromEntries(entries)) }) return () => { cancelled = true } }, [enabled, actionIdsKey, openActions]) return ( ) }