1+ "use client" ;
2+
3+ import { useState , useEffect } from "react" ;
4+ import { useParams } from "next/navigation" ;
5+ import { ArrowLeft , Clock , CheckCircle , XCircle , AlertCircle , Eye , Plus , Github , FolderGit2 } from "lucide-react" ;
6+ import Link from "next/link" ;
7+ import { Button } from "@/components/ui/button" ;
8+ import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card" ;
9+ import { Badge } from "@/components/ui/badge" ;
10+ import { ProtectedRoute } from "@/components/protected-route" ;
11+ import { useAuth } from "@/contexts/auth-context" ;
12+ import { ApiService } from "@/lib/api-service" ;
13+ import { Task , Project } from "@/types" ;
14+
15+ interface TaskWithProject extends Task {
16+ project ?: Project
17+ }
18+
19+ export default function ProjectTasksPage ( ) {
20+ const { user } = useAuth ( ) ;
21+ const params = useParams ( ) ;
22+ const projectId = parseInt ( params . id as string ) ;
23+
24+ const [ project , setProject ] = useState < Project | null > ( null ) ;
25+ const [ tasks , setTasks ] = useState < TaskWithProject [ ] > ( [ ] ) ;
26+ const [ loading , setLoading ] = useState ( true ) ;
27+
28+ useEffect ( ( ) => {
29+ if ( user ?. id && projectId ) {
30+ loadProject ( ) ;
31+ loadTasks ( ) ;
32+ }
33+ } , [ user ?. id , projectId ] ) ;
34+
35+ const loadProject = async ( ) => {
36+ if ( ! user ?. id ) return ;
37+
38+ try {
39+ const projectData = await ApiService . getProject ( user . id , projectId ) ;
40+ setProject ( projectData ) ;
41+ } catch ( error ) {
42+ console . error ( 'Error loading project:' , error ) ;
43+ }
44+ } ;
45+
46+ const loadTasks = async ( ) => {
47+ if ( ! user ?. id ) return ;
48+
49+ try {
50+ setLoading ( true ) ;
51+ const taskData = await ApiService . getTasks ( user . id , projectId ) ;
52+ setTasks ( taskData ) ;
53+ } catch ( error ) {
54+ console . error ( 'Error loading tasks:' , error ) ;
55+ } finally {
56+ setLoading ( false ) ;
57+ }
58+ } ;
59+
60+ const getStatusVariant = ( status : string ) => {
61+ switch ( status ) {
62+ case "pending" : return "secondary" ;
63+ case "running" : return "default" ;
64+ case "completed" : return "default" ;
65+ case "failed" : return "destructive" ;
66+ default : return "outline" ;
67+ }
68+ } ;
69+
70+ const getStatusIcon = ( status : string ) => {
71+ switch ( status ) {
72+ case "pending" : return < Clock className = "w-4 h-4" /> ;
73+ case "running" : return < AlertCircle className = "w-4 h-4" /> ;
74+ case "completed" : return < CheckCircle className = "w-4 h-4" /> ;
75+ case "failed" : return < XCircle className = "w-4 h-4" /> ;
76+ default : return null ;
77+ }
78+ } ;
79+
80+ if ( loading ) {
81+ return (
82+ < ProtectedRoute >
83+ < div className = "min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center" >
84+ < div className = "text-center" >
85+ < div className = "animate-spin rounded-full h-8 w-8 border-b-2 border-slate-900 mx-auto" > </ div >
86+ < p className = "text-slate-600 mt-2" > Loading tasks...</ p >
87+ </ div >
88+ </ div >
89+ </ ProtectedRoute >
90+ ) ;
91+ }
92+
93+ if ( ! project ) {
94+ return (
95+ < ProtectedRoute >
96+ < div className = "min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center" >
97+ < div className = "text-center" >
98+ < XCircle className = "w-16 h-16 text-slate-300 mx-auto mb-4" />
99+ < h3 className = "text-xl font-semibold text-slate-900 mb-2" > Project Not Found</ h3 >
100+ < p className = "text-slate-600 mb-6" > The project you're looking for doesn't exist or you don't have access to it.</ p >
101+ < Link href = "/projects" >
102+ < Button > Back to Projects</ Button >
103+ </ Link >
104+ </ div >
105+ </ div >
106+ </ ProtectedRoute >
107+ ) ;
108+ }
109+
110+ return (
111+ < ProtectedRoute >
112+ < div className = "min-h-screen bg-gradient-to-br from-slate-50 to-slate-100" >
113+ { /* Header */ }
114+ < header className = "border-b bg-white/80 backdrop-blur-sm sticky top-0 z-50" >
115+ < div className = "container mx-auto px-6 py-4" >
116+ < div className = "flex items-center justify-between" >
117+ < div className = "flex items-center gap-4" >
118+ < Link href = "/projects" className = "text-slate-600 hover:text-slate-900 flex items-center gap-2" >
119+ < ArrowLeft className = "w-4 h-4" />
120+ Back to Projects
121+ </ Link >
122+ < div >
123+ < h1 className = "text-xl font-semibold text-slate-900 flex items-center gap-2" >
124+ < FolderGit2 className = "w-5 h-5" />
125+ { project . name } Tasks
126+ </ h1 >
127+ < p className = "text-sm text-slate-500 flex items-center gap-1" >
128+ < Github className = "w-3 h-3" />
129+ { project . repo_owner } /{ project . repo_name }
130+ </ p >
131+ </ div >
132+ </ div >
133+ < Link href = { `/?project=${ projectId } ` } >
134+ < Button className = "gap-2" >
135+ < Plus className = "w-4 h-4" />
136+ New Task
137+ </ Button >
138+ </ Link >
139+ </ div >
140+ </ div >
141+ </ header >
142+
143+ { /* Main Content */ }
144+ < main className = "container mx-auto px-6 py-8 max-w-4xl" >
145+ { /* Project Info */ }
146+ < Card className = "mb-6" >
147+ < CardContent className = "pt-6" >
148+ < div className = "grid grid-cols-1 md:grid-cols-3 gap-4" >
149+ < div className = "text-center p-4 bg-slate-50 rounded-lg" >
150+ < div className = "text-2xl font-bold text-slate-900" > { tasks . length } </ div >
151+ < div className = "text-sm text-slate-500" > Total Tasks</ div >
152+ </ div >
153+ < div className = "text-center p-4 bg-green-50 rounded-lg" >
154+ < div className = "text-2xl font-bold text-green-700" >
155+ { tasks . filter ( t => t . status === 'completed' ) . length }
156+ </ div >
157+ < div className = "text-sm text-green-600" > Completed</ div >
158+ </ div >
159+ < div className = "text-center p-4 bg-blue-50 rounded-lg" >
160+ < div className = "text-2xl font-bold text-blue-700" >
161+ { tasks . filter ( t => t . status === 'running' || t . status === 'pending' ) . length }
162+ </ div >
163+ < div className = "text-sm text-blue-600" > Active</ div >
164+ </ div >
165+ </ div >
166+ </ CardContent >
167+ </ Card >
168+
169+ { /* Tasks List */ }
170+ < Card >
171+ < CardHeader >
172+ < CardTitle > All Tasks</ CardTitle >
173+ < CardDescription >
174+ Automation tasks for this project
175+ </ CardDescription >
176+ </ CardHeader >
177+ < CardContent >
178+ { tasks . length === 0 ? (
179+ < div className = "text-center py-12" >
180+ < AlertCircle className = "w-16 h-16 text-slate-300 mx-auto mb-4" />
181+ < h3 className = "text-xl font-semibold text-slate-900 mb-2" > No tasks yet</ h3 >
182+ < p className = "text-slate-600 mb-6" > Start your first automation task for this project</ p >
183+ < Link href = { `/?project=${ projectId } ` } >
184+ < Button size = "lg" className = "gap-2" >
185+ < Plus className = "w-4 h-4" />
186+ Create First Task
187+ </ Button >
188+ </ Link >
189+ </ div >
190+ ) : (
191+ < div className = "space-y-4" >
192+ { tasks . map ( ( task ) => (
193+ < div key = { task . id } className = "flex items-center justify-between p-4 border rounded-lg hover:bg-slate-50" >
194+ < div className = "flex-1 min-w-0" >
195+ < div className = "flex items-center gap-3 mb-2" >
196+ < Badge variant = { getStatusVariant ( task . status || '' ) } className = "gap-1" >
197+ { getStatusIcon ( task . status || '' ) }
198+ { task . status }
199+ </ Badge >
200+ < span className = "text-sm text-slate-500" >
201+ Task #{ task . id }
202+ </ span >
203+ < span className = "text-sm text-slate-500" >
204+ { task . agent ?. toUpperCase ( ) }
205+ </ span >
206+ </ div >
207+ < p className = "text-sm font-medium text-slate-900 truncate mb-1" >
208+ { task . chat_messages ?. [ 0 ] ?. content || 'No prompt available' }
209+ </ p >
210+ < div className = "flex items-center gap-4 text-xs text-slate-500" >
211+ < span > Created: { new Date ( task . created_at || '' ) . toLocaleString ( ) } </ span >
212+ { task . completed_at && (
213+ < span > Completed: { new Date ( task . completed_at ) . toLocaleString ( ) } </ span >
214+ ) }
215+ { task . commit_hash && (
216+ < span > Commit: { task . commit_hash . substring ( 0 , 8 ) } </ span >
217+ ) }
218+ { task . pr_number && (
219+ < span > PR: #{ task . pr_number } </ span >
220+ ) }
221+ </ div >
222+ </ div >
223+ < div className = "flex items-center gap-2" >
224+ { task . pr_url && (
225+ < Button variant = "outline" size = "sm" asChild >
226+ < a href = { task . pr_url } target = "_blank" rel = "noopener noreferrer" >
227+ < Github className = "w-3 h-3" />
228+ </ a >
229+ </ Button >
230+ ) }
231+ < Link href = { `/tasks/${ task . id } ` } >
232+ < Button variant = "outline" size = "sm" >
233+ < Eye className = "w-4 h-4" />
234+ </ Button >
235+ </ Link >
236+ </ div >
237+ </ div >
238+ ) ) }
239+ </ div >
240+ ) }
241+ </ CardContent >
242+ </ Card >
243+ </ main >
244+ </ div >
245+ </ ProtectedRoute >
246+ ) ;
247+ }
0 commit comments