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 (
{openActions.map((action) => (
))}
)
}