import { useState, useEffect, useRef, useCallback, useMemo } from "react"; import { Settings, Pause, Play, Square, LayoutGrid, List, Terminal, Lightbulb, Search, X, Activity, MoreHorizontal, Clock, Folder, History, GitBranch, Workflow, Bot, ChevronLeft, Target, Building2, ChevronRight, FileCode, Loader2 } from "lucide-react"; import type { ProjectInfo } from "../api"; import { fetchScripts } from "../api"; import { ProjectSelector } from "./ProjectSelector"; import { QuickScriptsDropdown } from "./QuickScriptsDropdown"; // GitHub logo icon (Octocat mark) - uses currentColor for theme compatibility function GitHubLogo({ size = 16 }: { size?: number }) { return ( ); } export interface HeaderProps { onOpenSettings?: () => void; onOpenGitHubImport?: () => void; onOpenPlanning?: () => void; onOpenUsage?: () => void; onOpenActivityLog?: () => void; onOpenSchedules?: () => void; onOpenGitManager?: () => void; onOpenWorkflowSteps?: () => void; onOpenMissions?: () => void; onOpenScripts?: () => void; onRunScript?: (name: string, command: string) => void; onToggleTerminal?: () => void; /** Opens the top-level workspace-aware file browser modal. */ onOpenFiles?: () => void; filesOpen?: boolean; globalPaused?: boolean; enginePaused?: boolean; onToggleGlobalPause?: () => void; onToggleEnginePause?: () => void; view?: "board" | "list" | "agents"; onChangeView?: (view: "board" | "list" | "agents") => void; searchQuery?: string; onSearchChange?: (query: string) => void; /** Multi-project props */ projects?: ProjectInfo[]; currentProject?: ProjectInfo | null; onSelectProject?: (project: ProjectInfo) => void; onViewAllProjects?: () => void; projectId?: string; } type ViewportMode = "mobile" | "tablet" | "desktop"; function getViewportMode(): ViewportMode { if (typeof window === "undefined") return "desktop"; if (window.matchMedia("(max-width: 768px)").matches) return "mobile"; if (window.matchMedia("(min-width: 769px) and (max-width: 1024px)").matches) return "tablet"; return "desktop"; } function useViewportMode(): ViewportMode { const [mode, setMode] = useState(getViewportMode); useEffect(() => { if (typeof window === "undefined") return; const mobileQuery = window.matchMedia("(max-width: 768px)"); const tabletQuery = window.matchMedia("(min-width: 769px) and (max-width: 1024px)"); const updateMode = () => { if (mobileQuery.matches) { setMode("mobile"); } else if (tabletQuery.matches) { setMode("tablet"); } else { setMode("desktop"); } }; mobileQuery.addEventListener("change", updateMode); tabletQuery.addEventListener("change", updateMode); return () => { mobileQuery.removeEventListener("change", updateMode); tabletQuery.removeEventListener("change", updateMode); }; }, []); return mode; } export function Header({ onOpenSettings, onOpenGitHubImport, onOpenPlanning, onOpenUsage, onOpenActivityLog, onOpenSchedules, onOpenGitManager, onOpenWorkflowSteps, onOpenMissions, onOpenScripts, onRunScript, onToggleTerminal, onOpenFiles, filesOpen, globalPaused, enginePaused, onToggleGlobalPause, onToggleEnginePause, view = "board", onChangeView, searchQuery = "", onSearchChange, projects = [], currentProject, onSelectProject, onViewAllProjects, projectId, }: HeaderProps) { const mode = useViewportMode(); const isMobile = mode === "mobile"; const isTablet = mode === "tablet"; const isCompact = isMobile || isTablet; const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false); const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false); const [isTerminalSubmenuOpen, setIsTerminalSubmenuOpen] = useState(false); const [overflowScripts, setOverflowScripts] = useState>({}); const [overflowScriptsLoading, setOverflowScriptsLoading] = useState(false); const overflowButtonRef = useRef(null); const overflowMenuRef = useRef(null); const mobileSearchRef = useRef(null); const mobileSearchInputRef = useRef(null); const terminalSubmenuOpenRef = useRef(false); // Script entries sorted alphabetically for overflow submenu const overflowScriptEntries = useMemo(() => { return Object.entries(overflowScripts).sort(([a], [b]) => a.localeCompare(b)); }, [overflowScripts]); // Keep ref in sync with state useEffect(() => { terminalSubmenuOpenRef.current = isTerminalSubmenuOpen; }, [isTerminalSubmenuOpen]); // Fetch scripts when terminal submenu opens in compact mode useEffect(() => { if (!isTerminalSubmenuOpen || !isCompact) return; let cancelled = false; setOverflowScriptsLoading(true); fetchScripts(projectId) .then((data) => { if (!cancelled) { setOverflowScripts(data); } }) .catch(() => { if (!cancelled) { setOverflowScripts({}); } }) .finally(() => { if (!cancelled) { setOverflowScriptsLoading(false); } }); return () => { cancelled = true; }; }, [isTerminalSubmenuOpen, isCompact, projectId]); // Keep mobile search open if there's an active search query const shouldShowMobileSearch = isMobileSearchOpen || searchQuery.length > 0; // Close overflow menu on outside click useEffect(() => { if (!isOverflowMenuOpen) return; const handleClickOutside = (e: MouseEvent) => { if ( overflowMenuRef.current && !overflowMenuRef.current.contains(e.target as Node) && overflowButtonRef.current && !overflowButtonRef.current.contains(e.target as Node) ) { setIsOverflowMenuOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [isOverflowMenuOpen]); // Close menus on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { if (terminalSubmenuOpenRef.current) { setIsTerminalSubmenuOpen(false); return; } setIsOverflowMenuOpen(false); setIsMobileSearchOpen(false); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, []); // Focus input when mobile search opens useEffect(() => { if (isMobileSearchOpen && mobileSearchInputRef.current) { setTimeout(() => mobileSearchInputRef.current?.focus(), 0); } }, [isMobileSearchOpen]); const handleMobileSearchToggle = useCallback(() => { setIsMobileSearchOpen((prev) => !prev); }, []); const handleOverflowToggle = useCallback(() => { setIsOverflowMenuOpen((prev) => !prev); }, []); const handleOverflowAction = useCallback((callback?: () => void) => { if (callback) callback(); setIsOverflowMenuOpen(false); setIsTerminalSubmenuOpen(false); }, []); const handleMobileSearchClose = useCallback(() => { setIsMobileSearchOpen(false); if (onSearchChange) onSearchChange(""); }, [onSearchChange]); return (

Fusion

{/* Project Selector - shown when 2+ projects on desktop only */} {!isCompact && projects.length > 1 && (
{ onSelectProject?.(project); }} onViewAll={onViewAllProjects || (() => {})} />
)} {/* Back to All Projects button when viewing a specific project (desktop only) */} {!isCompact && currentProject && onViewAllProjects && ( )}
{/* Desktop Search - only show in board view */} {onSearchChange && view === "board" && !isMobile && (
onSearchChange(e.target.value)} className="header-search-input" /> {searchQuery && ( )}
)} {/* Mobile Search Trigger - only show in board view on mobile */} {onSearchChange && view === "board" && isMobile && ( <> {!shouldShowMobileSearch ? ( ) : (
onSearchChange(e.target.value)} className="header-search-input" />
)} )} {/* View Toggle - always inline, even on mobile */} {onChangeView && (
)} {/* Usage button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenUsage && ( )} {/* Activity Log button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenActivityLog && ( )} {/* Desktop actions */} {!isCompact && ( )} {!isCompact && ( )} {/* Schedules button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && ( )} {/* Terminal button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && ( )} {/* Files button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenFiles && ( )} {/* Git Manager button - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenGitManager && ( )} {/* Workflow Steps - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenWorkflowSteps && ( )} {/* Missions - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenMissions && ( )} {/* Scripts - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenScripts && onRunScript && ( )} {/* Settings - always inline on desktop only (overflow on mobile/tablet) */} {!isCompact && ( )} {/* Pause button (soft pause) - always inline */} {/* Stop button (hard stop) - always inline */} {/* Compact overflow menu trigger (mobile + tablet) */} {isCompact && ( )} {/* Compact overflow menu (mobile + tablet) */} {isCompact && isOverflowMenuOpen && (
{/* Switch Project - in overflow on mobile */} {projects.length > 1 && onViewAllProjects && ( )} {/* Files - in overflow on mobile */} {onOpenFiles && ( )} {/* Git Manager - in overflow on mobile */} {onOpenGitManager && ( )}
{isTerminalSubmenuOpen && (
{overflowScriptsLoading ? (
Loading scripts…
) : overflowScriptEntries.length > 0 ? ( <> {overflowScriptEntries.map(([name, command]) => ( ))} {onOpenScripts && ( )} ) : ( onOpenScripts && ( ) )}
)}
{/* Activity Log - in overflow on mobile */} {onOpenActivityLog && ( )} {/* Usage - in overflow on mobile */} {onOpenUsage && ( )} {/* Workflow Steps - in overflow on mobile */} {onOpenWorkflowSteps && ( )} {/* Missions - in overflow on mobile */} {onOpenMissions && ( )} {/* Settings - always last in overflow menu */}
)}
); }