import { useEffect, useState, type ReactElement } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { MissionTimeline } from '@/components/projects/mission-timeline'; import { PrdViewer } from '@/components/projects/prd-viewer'; import { TaskDetailModal } from '@/components/tasks/task-detail-modal'; import { TaskListView } from '@/components/tasks/task-list-view'; import { TaskStatusSummary } from '@/components/tasks/task-status-summary'; import { api } from '@/lib/api'; import { cn } from '@/lib/cn'; import type { Mission, Project, Task, TaskStatus } from '@/lib/types'; import { getErrorMessage } from './page-errors'; type Tab = 'overview' | 'tasks' | 'missions' | 'prd'; const projectStatusColors: Record = { active: 'bg-success/20 text-success', paused: 'bg-warning/20 text-warning', completed: 'bg-blue-600/20 text-blue-400', archived: 'bg-gray-600/20 text-gray-400', }; const taskStatusColors: Record = { 'not-started': 'bg-gray-600/20 text-gray-300', 'in-progress': 'bg-blue-600/20 text-blue-400', blocked: 'bg-error/20 text-error', done: 'bg-success/20 text-success', cancelled: 'bg-gray-600/20 text-gray-500', }; interface TabButtonProps { id: Tab; label: string; activeTab: Tab; onClick: (tab: Tab) => void; } function TabButton({ id, label, activeTab, onClick }: TabButtonProps): ReactElement { return ( ); } export function ProjectDetailPage(): ReactElement { const { id = '' } = useParams(); const navigate = useNavigate(); const [project, setProject] = useState(null); const [missions, setMissions] = useState([]); const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState('overview'); const [taskFilter, setTaskFilter] = useState('all'); const [selectedTask, setSelectedTask] = useState(null); useEffect(() => { if (!id) { setError('Project id is missing.'); setLoading(false); return; } let cancelled = false; setLoading(true); setError(null); void Promise.all([ api('/api/projects/' + id), api('/api/missions').catch(() => [] as Mission[]), api('/api/tasks?projectId=' + id).catch(() => [] as Task[]), ]) .then(([loadedProject, allMissions, loadedTasks]) => { if (cancelled) return; setProject(loadedProject); setMissions(allMissions.filter((mission) => mission.projectId === id)); setTasks(loadedTasks); }) .catch((caught: unknown) => { if (cancelled) return; setError(getErrorMessage(caught, 'Failed to load project.')); }) .finally(() => { if (cancelled) return; setLoading(false); }); return () => { cancelled = true; }; }, [id]); if (loading) { return (

Project

Loading project...

); } if (error || !project) { return (

Project

{error ?? 'Project not found.'}
); } const filteredTasks = taskFilter === 'all' ? tasks : tasks.filter((task) => task.status === taskFilter); const prdContent = getPrdContent(project); const tabs: Array<{ id: Tab; label: string }> = [ { id: 'overview', label: 'Overview' }, { id: 'tasks', label: `Tasks (${tasks.length})` }, { id: 'missions', label: `Missions (${missions.length})` }, ...(prdContent ? [{ id: 'prd' as const, label: 'PRD' }] : []), ]; return (

{project.name}

{project.status}
{project.description ? (

{project.description}

) : null}

Created {new Date(project.createdAt).toLocaleDateString()} ยท Updated{' '} {new Date(project.updatedAt).toLocaleDateString()}

task.status === 'done').length)} valueClass="text-success" /> task.status === 'in-progress').length)} valueClass="text-blue-400" /> task.status === 'blocked').length)} valueClass={tasks.some((task) => task.status === 'blocked') ? 'text-error' : undefined} />
{tabs.map((tab) => ( ))}
{activeTab === 'overview' ? ( ) : null} {activeTab === 'tasks' ? (
) : null} {activeTab === 'missions' ? : null} {activeTab === 'prd' && prdContent ? (
) : null} {selectedTask ? ( setSelectedTask(null)} /> ) : null}
); } function OverviewTab({ project, missions, tasks, }: { project: Project; missions: Mission[]; tasks: Task[]; }): ReactElement { const recentTasks = [...tasks] .sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime()) .slice(0, 5); return (

Recent Tasks

{recentTasks.length === 0 ? (

No tasks yet

) : (
{recentTasks.map((task) => (
{task.title} {task.status}
))}
)}

Missions

{missions.length === 0 ? (

No missions yet

) : ( )}
{project.metadata && Object.keys(project.metadata).length > 0 ? (

Project Metadata

              {JSON.stringify(project.metadata, null, 2)}
            
) : null}
); } function StatCard({ label, value, valueClass, }: { label: string; value: string; valueClass?: string; }): ReactElement { return (

{label}

{value}

); } function getPrdContent(project: Project): string | null { if (!project.metadata) return null; const prd = project.metadata['prd']; if (typeof prd === 'string' && prd.trim().length > 0) { return prd; } const prdContent = project.metadata['prdContent']; if (typeof prdContent === 'string' && prdContent.trim().length > 0) { return prdContent; } return null; }