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 { PLAN_OUTLINE_NODES, resolvePlanOutlineActiveKey, resolvePlanOutlineProjectId, resolvePlanOutlineActionId, } from '../plan/planOutlineNodes.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 ( ) } 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 [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) { 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 ( ) }