project-detail-header.tsx
1 'use client' 2 3 import { useMemo } from 'react' 4 import { useAppStore } from '@/stores/use-app-store' 5 import type { Agent, BoardTask, Project } from '@/types' 6 7 const TABS = [ 8 { key: 'overview', label: 'Overview' }, 9 { key: 'work', label: 'Work' }, 10 { key: 'operations', label: 'Operations' }, 11 { key: 'activity', label: 'Activity' }, 12 ] as const 13 14 interface ProjectDetailHeaderProps { 15 project: Project 16 failedCount: number 17 blockedCount: number 18 credentialReqCount: number 19 } 20 21 export function ProjectDetailHeader({ project, failedCount, blockedCount, credentialReqCount }: ProjectDetailHeaderProps) { 22 const setEditingProjectId = useAppStore((s) => s.setEditingProjectId) 23 const setProjectSheetOpen = useAppStore((s) => s.setProjectSheetOpen) 24 const activeTab = useAppStore((s) => s.projectDetailTab) 25 const setActiveTab = useAppStore((s) => s.setProjectDetailTab) 26 const agents = useAppStore((s) => s.agents) as Record<string, Agent> 27 const tasks = useAppStore((s) => s.tasks) as Record<string, BoardTask> 28 const activeProjectFilter = useAppStore((s) => s.activeProjectFilter) 29 30 const projectAgentCount = useMemo( 31 () => Object.values(agents).filter((a) => a.projectId === activeProjectFilter && !a.trashedAt).length, 32 [agents, activeProjectFilter], 33 ) 34 35 const { totalTasks, completedTasks, progressPct } = useMemo(() => { 36 const pt = Object.values(tasks).filter((t) => t.projectId === activeProjectFilter) 37 const completed = pt.filter((t) => t.status === 'completed').length 38 const total = pt.length 39 return { 40 totalTasks: total, 41 completedTasks: completed, 42 progressPct: total > 0 ? Math.round((completed / total) * 100) : 0, 43 } 44 }, [tasks, activeProjectFilter]) 45 46 const workBadge = failedCount + blockedCount 47 const opsBadge = credentialReqCount 48 49 return ( 50 <div className="shrink-0 border-b border-white/[0.06]"> 51 {/* Project identity */} 52 <div className="px-8 pt-6 pb-4"> 53 <div className="flex items-center gap-3"> 54 <div 55 className="w-8 h-8 rounded-[10px] flex items-center justify-center shrink-0 text-[14px] font-700 text-white/90" 56 style={{ backgroundColor: project.color || '#6366F1' }} 57 > 58 {project.name.charAt(0).toUpperCase()} 59 </div> 60 <h1 className="font-display text-[22px] font-700 text-text tracking-[-0.02em] truncate flex-1"> 61 {project.name} 62 </h1> 63 <button 64 onClick={() => { setEditingProjectId(project.id); setProjectSheetOpen(true) }} 65 className="shrink-0 p-1.5 rounded-[8px] hover:bg-white/[0.06] transition-colors cursor-pointer bg-transparent border-none text-text-3/50 hover:text-text-2" 66 > 67 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"> 68 <path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" /> 69 </svg> 70 </button> 71 </div> 72 {project.description && ( 73 <p className="text-[13px] text-text-3/60 mt-1.5 truncate">{project.description}</p> 74 )} 75 {/* Compact stat bar */} 76 <div className="flex items-center gap-1.5 mt-2.5 text-[11px] text-text-3/50"> 77 <span>{projectAgentCount} agent{projectAgentCount !== 1 ? 's' : ''}</span> 78 <span className="text-text-3/20">·</span> 79 <span>{totalTasks} task{totalTasks !== 1 ? 's' : ''}</span> 80 <span className="text-text-3/20">·</span> 81 <span>{completedTasks} completed</span> 82 <span className="text-text-3/20">·</span> 83 <span className={progressPct === 100 ? 'text-emerald-400' : ''}>{progressPct}%</span> 84 </div> 85 </div> 86 87 {/* Tab bar */} 88 <div className="px-8 flex items-center gap-1"> 89 {TABS.map((tab) => { 90 const isActive = activeTab === tab.key 91 const badge = tab.key === 'work' ? workBadge : tab.key === 'operations' ? opsBadge : 0 92 return ( 93 <button 94 key={tab.key} 95 onClick={() => setActiveTab(tab.key)} 96 className={`relative px-3.5 py-2.5 text-[12px] font-600 transition-colors cursor-pointer bg-transparent border-none 97 ${isActive ? 'text-text' : 'text-text-3/50 hover:text-text-2'}`} 98 style={{ fontFamily: 'inherit' }} 99 > 100 <span className="flex items-center gap-1.5"> 101 {tab.label} 102 {badge > 0 && ( 103 <span className="inline-flex items-center justify-center min-w-[16px] h-[16px] px-1 rounded-full bg-red-500/15 text-red-400 text-[9px] font-700"> 104 {badge} 105 </span> 106 )} 107 </span> 108 {isActive && ( 109 <div className="absolute bottom-0 left-3.5 right-3.5 h-[2px] rounded-full" style={{ backgroundColor: project.color || '#6366F1' }} /> 110 )} 111 </button> 112 ) 113 })} 114 </div> 115 </div> 116 ) 117 }