diff --git a/async-code-web/app/page.tsx b/async-code-web/app/page.tsx index 3560661..caa1910 100644 --- a/async-code-web/app/page.tsx +++ b/async-code-web/app/page.tsx @@ -41,6 +41,10 @@ export default function Home() { const [isLoading, setIsLoading] = useState(false); const [showNotification, setShowNotification] = useState(false); const [notificationMessage, setNotificationMessage] = useState(""); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [hasMoreTasks, setHasMoreTasks] = useState(true); + const [taskPage, setTaskPage] = useState(0); + const TASKS_PER_PAGE = 10; // Initialize GitHub token from localStorage useEffect(() => { @@ -124,17 +128,50 @@ export default function Home() { } }; - const loadTasks = async () => { + const loadTasks = async (reset: boolean = true) => { if (!user?.id) return; try { - const taskData = await SupabaseService.getTasks(); - setTasks(taskData); + const taskData = await SupabaseService.getTasks(undefined, { + limit: TASKS_PER_PAGE, + offset: 0 + }); + + if (reset) { + setTasks(taskData); + setTaskPage(0); + setHasMoreTasks(taskData.length === TASKS_PER_PAGE); + } } catch (error) { console.error('Error loading tasks:', error); } }; + const loadMoreTasks = async () => { + if (!user?.id || isLoadingMore || !hasMoreTasks) return; + + try { + setIsLoadingMore(true); + const nextPage = taskPage + 1; + const taskData = await SupabaseService.getTasks(undefined, { + limit: TASKS_PER_PAGE, + offset: nextPage * TASKS_PER_PAGE + }); + + if (taskData.length > 0) { + setTasks(prev => [...prev, ...taskData]); + setTaskPage(nextPage); + setHasMoreTasks(taskData.length === TASKS_PER_PAGE); + } else { + setHasMoreTasks(false); + } + } catch (error) { + console.error('Error loading more tasks:', error); + } finally { + setIsLoadingMore(false); + } + }; + const handleStartTask = async () => { if (!prompt.trim() || !githubToken.trim()) { toast.error('Please provide both a prompt and GitHub token'); @@ -493,15 +530,15 @@ export default function Home() {
All Tasks - - - +
+ {tasks.length} tasks loaded +
Track all your automation tasks
- + {tasks.length === 0 ? (
@@ -509,60 +546,90 @@ export default function Home() {

Start your first automation above

) : ( - tasks.slice(0, 10).map((task) => ( -
-
-
- - {task.pr_url && task.pr_number && ( - - )} - - #{task.id} • {getAgentIcon(task.agent || '')} {task.agent?.toUpperCase()} - + <> +
+ {tasks.map((task) => ( +
+
+
+ + {task.pr_url && task.pr_number && ( + + )} + + #{task.id} • {getAgentIcon(task.agent || '')} {task.agent?.toUpperCase()} + +
+

+ {(task.chat_messages as any[])?.[0]?.content?.substring(0, 50) || ''}... +

+
+ {task.project ? ( + <> + + {task.project.repo_name} + + ) : ( + <> + + Custom + + )} + + {new Date(task.created_at || '').toLocaleDateString()} +
+
+
+ {task.status === "completed" && ( + + )} + {task.status === "running" && ( +
+ +
+ )} + + + +
-

- {(task.chat_messages as any[])?.[0]?.content?.substring(0, 50) || ''}... -

-
- {task.project ? ( + ))} +
+ + {/* Load More Button */} + {hasMoreTasks && ( +
+
+
-
- {task.status === "completed" && ( - - )} - {task.status === "running" && ( -
- -
- )} - - - -
-
- )) + )} + )} diff --git a/async-code-web/app/tasks/page.tsx b/async-code-web/app/tasks/page.tsx deleted file mode 100644 index 46ffc6f..0000000 --- a/async-code-web/app/tasks/page.tsx +++ /dev/null @@ -1,481 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { ArrowLeft, Github, Clock, CheckCircle, XCircle, AlertCircle, Eye, Code2, Filter, Search, Calendar, FolderGit2, ExternalLink, RefreshCw } from "lucide-react"; -import Link from "next/link"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { ProtectedRoute } from "@/components/protected-route"; -import { TaskStatusBadge } from "@/components/task-status-badge"; -import { PRStatusBadge } from "@/components/pr-status-badge"; -import { useAuth } from "@/contexts/auth-context"; -import { ApiService } from "@/lib/api-service"; -import { SupabaseService } from "@/lib/supabase-service"; -import { Task, Project } from "@/types"; - -interface TaskWithProject extends Task { - project?: Project -} - -export default function TasksPage() { - const { user } = useAuth(); - const [tasks, setTasks] = useState([]); - const [projects, setProjects] = useState([]); - const [loading, setLoading] = useState(true); - const [searchQuery, setSearchQuery] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [projectFilter, setProjectFilter] = useState("all"); - const [sortBy, setSortBy] = useState("created_at"); - - useEffect(() => { - if (user?.id) { - loadData(); - } - }, [user?.id]); - - // Poll for status updates of running tasks - useEffect(() => { - if (!user?.id) return; - - const runningTasks = tasks.filter(task => task.status === "running" || task.status === "pending"); - if (runningTasks.length === 0) return; - - const interval = setInterval(async () => { - try { - const updatedTasks = await Promise.all( - runningTasks.map(task => SupabaseService.getTask(task.id)) - ); - - setTasks(prevTasks => - prevTasks.map(task => { - const updated = updatedTasks.find(t => t && t.id === task.id); - if (updated) { - return { ...task, ...updated }; - } - return task; - }) - ); - } catch (error) { - console.error('Error polling task status:', error); - } - }, 3000); - - return () => clearInterval(interval); - }, [tasks, user?.id]); - - const loadData = async () => { - if (!user?.id) return; - - try { - setLoading(true); - const [taskData, projectData] = await Promise.all([ - SupabaseService.getTasks(), - ApiService.getProjects(user.id) - ]); - - // Enhance tasks with project data - const tasksWithProjects = taskData.map((task: any) => ({ - ...task, - project: projectData.find(p => p.id === task.project_id) - })); - - setTasks(tasksWithProjects); - setProjects(projectData); - } catch (error) { - console.error('Error loading data:', error); - } finally { - setLoading(false); - } - }; - - const getStatusVariant = (status: string) => { - switch (status) { - case "pending": return "secondary"; - case "running": return "default"; - case "completed": return "default"; - case "failed": return "destructive"; - default: return "outline"; - } - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case "pending": return ; - case "running": return ; - case "completed": return ; - case "failed": return ; - default: return null; - } - }; - - const getPromptFromTask = (task: TaskWithProject): string => { - if (task.chat_messages && - Array.isArray(task.chat_messages) && - task.chat_messages.length > 0) { - const firstMessage = task.chat_messages[0]; - if (firstMessage && - typeof firstMessage === 'object' && - firstMessage !== null && - 'content' in firstMessage && - typeof firstMessage.content === 'string') { - return firstMessage.content; - } - } - return 'No prompt available'; - }; - - // Filter and sort tasks - const filteredTasks = tasks - .filter(task => { - // Search filter - if (searchQuery) { - const prompt = getPromptFromTask(task).toLowerCase(); - const projectName = task.project?.name?.toLowerCase() || ''; - const repoUrl = task.repo_url?.toLowerCase() || ''; - const query = searchQuery.toLowerCase(); - - if (!prompt.includes(query) && !projectName.includes(query) && !repoUrl.includes(query)) { - return false; - } - } - - // Status filter - if (statusFilter !== "all" && task.status !== statusFilter) { - return false; - } - - // Project filter - if (projectFilter !== "all") { - if (projectFilter === "no-project" && task.project_id) { - return false; - } - if (projectFilter !== "no-project" && task.project_id?.toString() !== projectFilter) { - return false; - } - } - - return true; - }) - .sort((a, b) => { - switch (sortBy) { - case "created_at": - return new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime(); - case "status": - return (a.status || '').localeCompare(b.status || ''); - case "project": - return (a.project?.name || '').localeCompare(b.project?.name || ''); - default: - return 0; - } - }); - - const statusCounts = { - all: tasks.length, - pending: tasks.filter(t => t.status === "pending").length, - running: tasks.filter(t => t.status === "running").length, - completed: tasks.filter(t => t.status === "completed").length, - failed: tasks.filter(t => t.status === "failed").length, - }; - - if (loading) { - return ( - -
-
-
-

Loading tasks...

-
-
-
- ); - } - - return ( - -
- {/* Header */} -
-
-
-
- - - Back to Dashboard - -
-

All Tasks

-

- {filteredTasks.length} of {tasks.length} tasks -

-
-
-
- - - - -
-
-
-
- - {/* Main Content */} -
- {/* Status Overview Cards */} -
- -
-
-

Total

-

{statusCounts.all}

-
- -
-
- -
-
-

Pending

-

{statusCounts.pending}

-
- -
-
- -
-
-

Running

-

{statusCounts.running}

-
- -
-
- -
-
-

Completed

-

{statusCounts.completed}

-
- -
-
- -
-
-

Failed

-

{statusCounts.failed}

-
- -
-
-
- - {/* Filters and Search */} - - - - - Filters - - - -
-
- -
- - setSearchQuery(e.target.value)} - className="pl-10" - /> -
-
-
- - -
-
- - -
-
- - -
-
-
-
- - {/* Tasks List */} - {filteredTasks.length === 0 ? ( - - - -

No tasks found

-

- {tasks.length === 0 - ? "You haven't created any tasks yet. Start by creating your first task." - : "No tasks match your current filters. Try adjusting your search criteria." - } -

- - - -
-
- ) : ( -
- {filteredTasks.map((task) => ( - - -
-
-
-
- -
-
-
-

- {getPromptFromTask(task)} -

- {task.pr_url && task.pr_number && ( -
- -
- )} -
- -
- - Task #{task.id} - - - - {task.agent?.toUpperCase()} - - {task.project && ( - <> - -
- - {task.project.repo_owner}/{task.project.repo_name} -
- - )} -
- -
-
- - - {task.repo_url} - -
-
- - {new Date(task.created_at || '').toLocaleDateString()} -
-
-
-
- - {task.error && ( -
- Error: {task.error} -
- )} - - {task.status === "running" && ( -
-
- -
- AI is working on this task... -
- )} -
- -
- - - - {task.pr_url && task.pr_number && ( - - )} -
-
-
-
- ))} -
- )} -
-
-
- ); -} \ No newline at end of file diff --git a/async-code-web/lib/supabase-service.ts b/async-code-web/lib/supabase-service.ts index 0d985d2..8a6ea20 100644 --- a/async-code-web/lib/supabase-service.ts +++ b/async-code-web/lib/supabase-service.ts @@ -87,7 +87,10 @@ export class SupabaseService { } // Task operations - static async getTasks(projectId?: number): Promise { + static async getTasks(projectId?: number, options?: { + limit?: number + offset?: number + }): Promise { // Get current authenticated user const { data: { user } } = await this.supabase.auth.getUser() if (!user) throw new Error('No authenticated user') @@ -109,6 +112,13 @@ export class SupabaseService { query = query.eq('project_id', projectId) } + // Add pagination if options provided + if (options?.limit) { + const start = options.offset || 0 + const end = start + options.limit - 1 + query = query.range(start, end) + } + const { data, error } = await query.order('created_at', { ascending: false }) if (error) throw error