/** * OverviewView - Multi-project dashboard showing status across all projects * * Provides a unified view of all projects with active features, running agents, * recent completions, and alerts. Quick navigation to any project or feature. */ import { useState, useCallback } from 'react'; import { useNavigate } from '@tanstack/react-router'; import { createLogger } from '@automaker/utils/logger'; import { useMultiProjectStatus } from '@/hooks/use-multi-project-status'; import { useAppStore } from '@/store/app-store'; import { isElectron, getElectronAPI } from '@/lib/electron'; import { isMac } from '@/lib/utils'; import { initializeProject } from '@/lib/project-init'; import { getHttpApiClient } from '@/lib/http-api-client'; import { toast } from 'sonner'; import { Spinner } from '@/components/ui/spinner'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { NewProjectModal } from '@/components/dialogs/new-project-modal'; import { WorkspacePickerModal } from '@/components/dialogs/workspace-picker-modal'; import { ProjectStatusCard } from './overview/project-status-card'; import { RecentActivityFeed } from './overview/recent-activity-feed'; import { RunningAgentsPanel } from './overview/running-agents-panel'; import type { StarterTemplate } from '@/lib/templates'; import { LayoutDashboard, RefreshCw, Folder, FolderOpen, Plus, Activity, CheckCircle2, XCircle, Clock, Bot, Bell, } from 'lucide-react'; const logger = createLogger('OverviewView'); export function OverviewView() { const navigate = useNavigate(); const { overview, isLoading, error, refresh } = useMultiProjectStatus(15000); // Refresh every 15s const { upsertAndSetCurrentProject } = useAppStore(); // Modal state const [showNewProjectModal, setShowNewProjectModal] = useState(false); const [showWorkspacePicker, setShowWorkspacePicker] = useState(false); const [isCreating, setIsCreating] = useState(false); const initializeAndOpenProject = useCallback( async (path: string, name: string) => { try { const initResult = await initializeProject(path); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } upsertAndSetCurrentProject(path, name); toast.success('Project opened', { description: `Opened ${name}` }); navigate({ to: '/board' }); } catch (error) { logger.error('[Overview] Failed to open project:', error); toast.error('Failed to open project', { description: error instanceof Error ? error.message : 'Unknown error', }); } }, [upsertAndSetCurrentProject, navigate] ); const handleOpenProject = useCallback(async () => { try { const httpClient = getHttpApiClient(); const configResult = await httpClient.workspace.getConfig(); if (configResult.success && configResult.configured) { setShowWorkspacePicker(true); } else { const api = getElectronAPI(); const result = await api.openDirectory(); if (!result.canceled && result.filePaths[0]) { const path = result.filePaths[0]; const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project'; await initializeAndOpenProject(path, name); } } } catch (error) { logger.error('[Overview] Failed to check workspace config:', error); const api = getElectronAPI(); const result = await api.openDirectory(); if (!result.canceled && result.filePaths[0]) { const path = result.filePaths[0]; const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project'; await initializeAndOpenProject(path, name); } } }, [initializeAndOpenProject]); const handleWorkspaceSelect = useCallback( async (path: string, name: string) => { setShowWorkspacePicker(false); await initializeAndOpenProject(path, name); }, [initializeAndOpenProject] ); const handleCreateBlankProject = useCallback( async (projectName: string, parentDir: string) => { setIsCreating(true); try { const api = getElectronAPI(); const projectPath = `${parentDir}/${projectName}`; await api.mkdir(projectPath); const initResult = await initializeProject(projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } await api.writeFile( `${projectPath}/.automaker/app_spec.txt`, ` ${projectName} Describe your project here. ` ); upsertAndSetCurrentProject(projectPath, projectName); setShowNewProjectModal(false); toast.success('Project created', { description: `Created ${projectName}` }); navigate({ to: '/board' }); } catch (error) { logger.error('Failed to create project:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }, [upsertAndSetCurrentProject, navigate] ); const handleCreateFromTemplate = useCallback( async (template: StarterTemplate, projectName: string, parentDir: string) => { setIsCreating(true); try { const httpClient = getHttpApiClient(); const cloneResult = await httpClient.templates.clone( template.repoUrl, projectName, parentDir ); if (!cloneResult.success || !cloneResult.projectPath) { toast.error('Failed to clone template', { description: cloneResult.error || 'Unknown error occurred', }); return; } const initResult = await initializeProject(cloneResult.projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } upsertAndSetCurrentProject(cloneResult.projectPath, projectName); setShowNewProjectModal(false); toast.success('Project created from template', { description: `Created ${projectName} from ${template.name}`, }); navigate({ to: '/board' }); } catch (error) { logger.error('Failed to create from template:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }, [upsertAndSetCurrentProject, navigate] ); const handleCreateFromCustomUrl = useCallback( async (repoUrl: string, projectName: string, parentDir: string) => { setIsCreating(true); try { const httpClient = getHttpApiClient(); const cloneResult = await httpClient.templates.clone(repoUrl, projectName, parentDir); if (!cloneResult.success || !cloneResult.projectPath) { toast.error('Failed to clone repository', { description: cloneResult.error || 'Unknown error occurred', }); return; } const initResult = await initializeProject(cloneResult.projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } upsertAndSetCurrentProject(cloneResult.projectPath, projectName); setShowNewProjectModal(false); toast.success('Project created from repository', { description: `Created ${projectName}` }); navigate({ to: '/board' }); } catch (error) { logger.error('Failed to create from custom URL:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }, [upsertAndSetCurrentProject, navigate] ); return (
{/* Header */}
{/* Electron titlebar drag region */} {isElectron() && (
{/* Main content */}
{/* Loading state */} {isLoading && !overview && (

Loading project overview...

)} {/* Error state */} {error && !overview && (

Failed to load overview

{error}

)} {/* Content */} {overview && (
{/* Aggregate stats */}

{overview.aggregate.projectCounts.total}

Projects

{overview.aggregate.featureCounts.running}

Running

{overview.aggregate.featureCounts.pending}

Pending

{overview.aggregate.featureCounts.completed}

Completed

{overview.aggregate.featureCounts.failed}

Failed

{overview.aggregate.projectsWithAutoModeRunning}

Auto-mode

{/* Main content grid */}
{/* Left column: Project cards */}

All Projects

{overview.aggregate.totalUnreadNotifications > 0 && (
{overview.aggregate.totalUnreadNotifications} unread notifications
)}
{overview.projects.length === 0 ? (

No projects yet

Create or open a project to get started

Use the sidebar to create or open a project

) : (
{overview.projects.map((project) => ( ))}
)}
{/* Right column: Running agents and activity */}
{/* Running agents */} Running Agents {overview.aggregate.projectsWithAutoModeRunning > 0 && ( {overview.aggregate.projectsWithAutoModeRunning} active )} {/* Recent activity */} Recent Activity
{/* Footer timestamp */}
Last updated:{' '} {new Date(overview.generatedAt).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', })}
)}
{/* Modals */}
); }