/ src / components / projects / project-detail-header.tsx
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">&middot;</span>
 79            <span>{totalTasks} task{totalTasks !== 1 ? 's' : ''}</span>
 80            <span className="text-text-3/20">&middot;</span>
 81            <span>{completedTasks} completed</span>
 82            <span className="text-text-3/20">&middot;</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  }