"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import { listCircuitLists, listDistributionBoards, listProjects } from "../utils/api"; import type { CircuitListDto, DistributionBoardDto, ProjectDto } from "../types"; import { ThemeToggle } from "./ThemeToggle"; import { useAuth } from "./AuthProvider"; import { getConfigEntry } from "../utils/config-access"; import { orderDistributionBoardsByFeed } from "../utils/distribution-board-hierarchy"; // Anchors on the project overview page (/projects/[id]). const PROJECT_SECTIONS = [ { id: "verlauf", label: "Verlauf", icon: "↺" }, { id: "verteilungen", label: "Verteilungen", icon: "⌁" }, { id: "projektgeraete", label: "Projektgeräte", icon: "⚙" }, ] as const; // Separate project sub-routes (/projects/[id]/). const PROJECT_ROUTES = [ { segment: "struktur", label: "Gebäudestruktur", icon: "▥" }, { segment: "grobbilanz", label: "Grobe Leistungsbilanz", icon: "Σ" }, { segment: "revit", label: "Revit", icon: "⇄" }, ] as const; // Matches the project overview and all of its sub-routes (group 2 = rest). const PROJECT_PATH_PATTERN = /^\/projects\/([^/]+)(\/.*)?$/; const CIRCUIT_LIST_PATH_PATTERN = /^\/circuit-lists\/([^/]+)(\/|$)/; const BOARDS_EXPANDED_STORAGE_KEY = "leistungsbilanz:sidebar-boards-expanded"; // Dispatched by the project page whenever its board list changes. const DISTRIBUTION_BOARDS_CHANGED_EVENT = "leistungsbilanz:distribution-boards-changed"; interface SidebarBoardLink { board: DistributionBoardDto; depth: number; circuitListId: string | null; } interface SidebarProps { isCollapsed: boolean; onToggleCollapsed: () => void; } export function Sidebar({ isCollapsed, onToggleCollapsed }: SidebarProps) { const pathname = usePathname(); const { user, isAdmin, logout } = useAuth(); const configEntry = getConfigEntry(isAdmin); const projectMatch = pathname?.match(PROJECT_PATH_PATTERN); const activeProjectId = projectMatch ? projectMatch[1] : null; const projectSubPath = projectMatch?.[2] ?? ""; // Section anchors only exist (and are observed) on the overview itself. const overviewProjectId = activeProjectId && (projectSubPath === "" || projectSubPath === "/") ? activeProjectId : null; const activeCircuitListId = projectSubPath.match(CIRCUIT_LIST_PATH_PATTERN)?.[1] ?? null; const [projects, setProjects] = useState([]); const [activeSection, setActiveSection] = useState(null); const [boardLinks, setBoardLinks] = useState([]); const [isBoardListExpanded, setIsBoardListExpanded] = useState(true); const activeProject = projects.find((project) => project.id === activeProjectId); useEffect(() => { try { setIsBoardListExpanded( window.localStorage.getItem(BOARDS_EXPANDED_STORAGE_KEY) !== "0" ); } catch { // Storage may be unavailable; keep the list expanded. } }, []); useEffect(() => { if (!activeProjectId) { setBoardLinks([]); return; } let isCurrent = true; function refreshBoards(projectId: string) { Promise.all([listDistributionBoards(projectId), listCircuitLists(projectId)]) .then(([boards, circuitLists]) => { if (isCurrent) { setBoardLinks(buildBoardLinks(boards, circuitLists)); } }) .catch(() => { if (isCurrent) { setBoardLinks([]); } }); } const projectId = activeProjectId; const handleBoardsChanged = () => refreshBoards(projectId); refreshBoards(projectId); window.addEventListener(DISTRIBUTION_BOARDS_CHANGED_EVENT, handleBoardsChanged); return () => { isCurrent = false; window.removeEventListener(DISTRIBUTION_BOARDS_CHANGED_EVENT, handleBoardsChanged); }; }, [activeProjectId]); function toggleBoardList() { setIsBoardListExpanded((current) => { const next = !current; try { window.localStorage.setItem(BOARDS_EXPANDED_STORAGE_KEY, next ? "1" : "0"); } catch { // Ignore unavailable storage; the toggle still works for this page. } return next; }); } useEffect(() => { function refreshProjects() { listProjects() .then(setProjects) .catch(() => setProjects([])); } refreshProjects(); window.addEventListener("leistungsbilanz:projects-changed", refreshProjects); return () => window.removeEventListener("leistungsbilanz:projects-changed", refreshProjects); }, []); useEffect(() => { if (!overviewProjectId) { setActiveSection(null); return; } const elements = PROJECT_SECTIONS.map((section) => document.getElementById(section.id) ).filter((element): element is HTMLElement => element !== null); if (!elements.length) { return; } const observer = new IntersectionObserver( (entries) => { const visible = entries .filter((entry) => entry.isIntersecting) .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); if (visible[0]) { setActiveSection(visible[0].target.id); } }, { rootMargin: "-20% 0px -70% 0px", threshold: 0 } ); elements.forEach((element) => observer.observe(element)); return () => observer.disconnect(); }, [overviewProjectId]); return ( ); } function buildBoardLinks( boards: DistributionBoardDto[], circuitLists: CircuitListDto[] ): SidebarBoardLink[] { const circuitListIdByBoardId = new Map( circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList.id]) ); return orderDistributionBoardsByFeed(boards).map(({ board, depth }) => ({ board, depth, circuitListId: circuitListIdByBoardId.get(board.id) ?? null, })); }