diff --git a/frontend/src/client/projects-detail.ts b/frontend/src/client/projects-detail.ts index 2b0de82..6619a58 100644 --- a/frontend/src/client/projects-detail.ts +++ b/frontend/src/client/projects-detail.ts @@ -1,6 +1,7 @@ import { initI18n, onLangChange, t, tDyn, getLang, translateEvent } from "./i18n"; import { initSidebar } from "./sidebar"; import { initNotes } from "./notes"; +import { initProjectTree, refreshProjectTree, rerenderProjectTree } from "./project-tree"; import { loadParentCandidates, initParentPicker, @@ -206,8 +207,9 @@ let deadlines: Deadline[] = []; let appointments: Appointment[] = []; let ancestors: ProjectMini[] = []; let children: ProjectMini[] = []; -type TreeNode = ProjectMini & { children?: TreeNode[] }; -let projectTree: TreeNode[] = []; +// projects-cards' /projects tree owns rendering inside the Projektbaum +// tab too — see initProjectTreeTab below. Local children[] still feeds +// the empty-state gate + the create-link parent_id pre-fill. let teamMembers: ProjectTeamMember[] = []; // t-paliad-139 — additional Team-tab sections. let descendantStaffed: ProjectTeamMember[] = []; @@ -1553,15 +1555,15 @@ async function loadChildren(id: string) { } catch { children = []; } - // Full visible tree drives the rendering. - try { - const resp = await fetch(`/api/projects/tree?subtree_counts=false`); - if (resp.ok) projectTree = ((await resp.json()) as TreeNode[]) ?? []; - } catch { - projectTree = []; - } } +// renderChildren is the Projektbaum tab's mount point. m's 2026-05-08 +// 21:28: "should just be the same as the Tree in Projects. It has +// symbols, everything." Reuse the /projects tree component +// (project-tree.ts) verbatim — type icons, pin stars, deadline badges, +// expand/collapse, search highlighting all come along for free. The +// current project is highlighted via a CSS modifier we add to its +// data-id row after the tree mounts. function renderChildren() { const root = document.getElementById("project-tree")!; const empty = document.getElementById("children-empty")!; @@ -1569,30 +1571,22 @@ function renderChildren() { // CTA next to the empty message is "create sub-project", which would be // misleading if the tree itself has other branches. empty.style.display = children.length ? "none" : ""; - if (!projectTree.length) { - root.innerHTML = ""; - return; - } - const currentId = project?.id ?? ""; - root.innerHTML = ``; -} - -function renderTreeNode(node: TreeNode, currentId: string, depth: number): string { - const isCurrent = node.id === currentId; - const kids = node.children ?? []; - const childHTML = kids.length - ? `` - : ""; - const itemClass = `projekt-tree-item${isCurrent ? " projekt-tree-item--current" : ""}`; - const linkInner = - `${esc(tDyn("projects.type." + node.type) || node.type)}` + - `${esc(node.title)}` + - (node.reference ? `${esc(node.reference)}` : "") + - `${esc(tDyn("projects.filter.status." + node.status) || node.status)}`; - const row = isCurrent - ? `${linkInner}` - : `${linkInner}`; - return `
  • ${row}${childHTML}
  • `; + // Mount the shared tree. initProjectTree fetches /api/projects/tree on + // first call and caches; subsequent tab-switches re-render from cache. + // Set aria-current on the row matching this project — the shared tree + // already styles aria-current=true with a lime highlight (global.css + // .projekt-tree-node[aria-current="true"] > .projekt-tree-row). + void initProjectTree(root).then(() => { + const currentId = project?.id ?? ""; + if (!currentId) return; + root.querySelectorAll(".projekt-tree-node").forEach((li) => { + if (li.dataset.id === currentId) { + li.setAttribute("aria-current", "true"); + } else { + li.removeAttribute("aria-current"); + } + }); + }); } function initChildAddLink() { diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 3ea2179..5365a96 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -9748,63 +9748,13 @@ dialog.quick-add-sheet::backdrop { opacity: 0.55; } -/* Projektbaum / Project Tree — full visible hierarchy on /projects/. */ -.projekt-tree { - margin-top: 0.5rem; -} - -.projekt-tree-list { - list-style: none; - padding-left: 0; - margin: 0; -} - -.projekt-tree-list .projekt-tree-list { - padding-left: 1.5rem; - border-left: 1px dashed var(--color-border-subtle, #e4e4e7); - margin-left: 0.6rem; -} - -.projekt-tree-item { - margin: 0.15rem 0; -} - -.projekt-tree-link { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.5rem; - border-radius: 6px; - text-decoration: none; - color: var(--color-text, var(--hlc-midnight)); - line-height: 1.3; -} - -.projekt-tree-link:hover, -.projekt-tree-link:focus-visible { - background: var(--color-bg-subtle); -} - -.projekt-tree-link--current { - background: var(--color-cta-lime-soft, #f0fad9); - border: 1px solid var(--color-cta-lime, #c6f41c); - font-weight: 600; - cursor: default; -} - -.projekt-tree-link--current:hover { - background: var(--color-cta-lime-soft, #f0fad9); -} - -.projekt-tree-title { - font-weight: 500; -} - -.projekt-tree-ref { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 0.85em; - color: var(--color-text-muted); -} +/* Projektbaum tab inside /projects/ reuses the /projects tree + component (client/project-tree.ts) — see initProjectTree(root) in + client/projects-detail.ts. The earlier inline mini-tree CSS at this + block was dead code that also accidentally overrode .projekt-tree-title, + so it was removed when m's "should be the same as the Tree in + Projects" landed (2026-05-08 21:28). The current-node highlight + already lives at .projekt-tree-node[aria-current="true"] above. */ @media (max-width: 640px) { .projekt-breadcrumb {