import { NavLink, useLocation } from 'react-router-dom'
import { useEffect, useMemo, useState } from 'react'
import { listInitiativeProjects } from '../api/projects.js'
import { listInitiativeActions } from '../api/initiatives.js'
import { listInitiativeBacklog } from '../api/backlog.js'
import { getInitiativeExecutionGraphState } from '../api/executionPlan.js'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
import { buildProjectsByParent } from '../utils/projectTree.js'
import { projectPath } from '../utils/routes.js'
import {
resolvePlanOutlineActiveKey,
resolvePlanOutlineProjectId,
resolvePlanOutlineActionId,
} from '../plan/planOutlineNodes.js'
import { resolvePlanOutlineNodes } from '../config/methodUiDefaults.js'
import { getInitiative } from '../api/initiatives.js'
import { getActiveWorkCycle } from '../api/workCycles.js'
import { PlanOutlineWorkTree } from './PlanOutlineWorkTree.jsx'
function PlanOutlineProjectTree({ projects, parentId, activeProjectId, depth = 0 }) {
const byParent = useMemo(() => buildProjectsByParent(projects), [projects])
const nodes = byParent.get(parentId || '') || []
if (nodes.length === 0) {
return null
}
return (
{nodes.map((project) => (
-
'analysis-split__nav-link analysis-split__nav-link--nested' +
(isActive || activeProjectId === project.id
? ' analysis-split__nav-link--active'
: '')
}
>
{project.title}
))}
)
}
export function PlanOutlineNav() {
const location = useLocation()
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
const activeProjectId = resolvePlanOutlineProjectId(location.pathname)
const activeActionId = resolvePlanOutlineActionId(location.pathname)
const [counts, setCounts] = useState({ inbox: null, work: null })
const [projects, setProjects] = useState([])
const [openActions, setOpenActions] = useState([])
const [executionGraph, setExecutionGraph] = useState(null)
const [archetypeKey, setArchetypeKey] = useState(null)
const [hasActiveSprint, setHasActiveSprint] = useState(false)
const [structureOpen, setStructureOpen] = useState(
activeKey === 'structure' || Boolean(activeProjectId),
)
const [workOpen, setWorkOpen] = useState(
activeKey === 'work' || Boolean(activeActionId),
)
useEffect(() => {
setStructureOpen(activeKey === 'structure' || Boolean(activeProjectId))
}, [activeKey, activeProjectId])
useEffect(() => {
setWorkOpen(activeKey === 'work' || Boolean(activeActionId))
}, [activeKey, activeActionId])
useEffect(() => {
if (!initiativeId) {
setArchetypeKey(null)
setHasActiveSprint(false)
return undefined
}
let cancelled = false
Promise.all([
getInitiative(initiativeId),
getActiveWorkCycle(initiativeId).catch(() => null),
])
.then(([initiative, activeCycle]) => {
if (cancelled) return
setArchetypeKey(initiative?.archetype_key || null)
setHasActiveSprint(Boolean(activeCycle?.id))
})
.catch(() => {
if (!cancelled) {
setArchetypeKey(null)
setHasActiveSprint(false)
}
})
return () => {
cancelled = true
}
}, [initiativeId])
const outlineNodes = useMemo(
() =>
resolvePlanOutlineNodes({
archetypeKey,
hasActiveSprint,
}),
[archetypeKey, hasActiveSprint],
)
useEffect(() => {
if (!initiativeId) {
setProjects([])
return undefined
}
let cancelled = false
listInitiativeProjects(initiativeId)
.then((items) => {
if (!cancelled) setProjects(Array.isArray(items) ? items : [])
})
.catch(() => {
if (!cancelled) setProjects([])
})
return () => {
cancelled = true
}
}, [initiativeId])
useEffect(() => {
if (!initiativeId) {
setCounts({ inbox: null, work: null })
return undefined
}
let cancelled = false
Promise.all([
listInitiativeBacklog(initiativeId).catch(() => []),
listInitiativeActions(initiativeId).catch(() => []),
getInitiativeExecutionGraphState(initiativeId).catch(() => null),
]).then(([backlog, actions, graphState]) => {
if (cancelled) return
const open = actions.filter(
(action) => action.status !== 'done' && action.status !== 'discarded',
)
setOpenActions(open)
setExecutionGraph(graphState)
setCounts({
inbox: backlog.filter((item) => item.status !== 'converted').length,
work: open.length,
})
})
return () => {
cancelled = true
}
}, [initiativeId, location.pathname])
function nodeLabel(node) {
if (node.key === 'inbox' && counts.inbox != null) {
return `${node.label} (${counts.inbox})`
}
if (node.key === 'work' && counts.work != null) {
return `${node.label} (${counts.work})`
}
if (node.key === 'structure' && projects.length > 0) {
return `${node.label} (${projects.length})`
}
return node.label
}
return (
)
}