"use client"; import { useEffect, useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { RefreshCw } from "lucide-react"; interface Task { id: string; headline: string; projectName: string; projectId: number; status: string; dueDate: string | null; milestone: string | null; details: string | null; } export function Flow() { const [tasks, setTasks] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [retryTimeout, setRetryTimeout] = useState(null); const fetchTasks = async (isRefresh = false) => { try { if (isRefresh) { setRefreshing(true); } const response = await fetch('/api/leantime/tasks'); if (response.status === 429) { const retryAfter = parseInt(response.headers.get('retry-after') || '60'); const timeout = setTimeout(() => fetchTasks(), retryAfter * 1000); setRetryTimeout(timeout); setError(`Rate limit exceeded. Retrying in ${retryAfter} seconds...`); return; } if (!response.ok) { throw new Error('Failed to fetch tasks'); } const data = await response.json(); if (data.tasks && Array.isArray(data.tasks)) { // Sort tasks by due date (overdue first) const sortedTasks = data.tasks.sort((a: Task, b: Task) => { // If a task has no due date, put it at the end if (!a.dueDate) return 1; if (!b.dueDate) return -1; const dateA = new Date(a.dueDate); const dateB = new Date(b.dueDate); const now = new Date(); // If one task is overdue and the other isn't, put overdue first const aOverdue = dateA < now; const bOverdue = dateB < now; if (aOverdue && !bOverdue) return -1; if (!aOverdue && bOverdue) return 1; // Otherwise sort by due date return dateA.getTime() - dateB.getTime(); }); setTasks(sortedTasks); } else { setTasks([]); } setError(null); } catch (err) { console.error('Error fetching tasks:', err); setError('Failed to fetch tasks'); } finally { setLoading(false); setRefreshing(false); } }; useEffect(() => { fetchTasks(); return () => { if (retryTimeout) { clearTimeout(retryTimeout); } }; }, []); const getStatusClass = (status: string): string => { switch (status.toLowerCase()) { case 'new': return 'bg-blue-100 text-blue-800'; case 'in_progress': return 'bg-yellow-100 text-yellow-800'; case 'completed': return 'bg-green-100 text-green-800'; case 'overdue': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; const formatDate = (dateString: string | null): string => { if (!dateString) return 'No due date'; const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }); }; // Group tasks by project, maintaining the sorted order const tasksByProject = tasks.reduce((acc, task) => { if (!acc[task.projectName]) { acc[task.projectName] = []; } acc[task.projectName].push(task); return acc; }, {} as Record); return ( Flow {loading ? (
) : error ? (
{error}
) : tasks.length === 0 ? (
No tasks found
) : (
{Object.entries(tasksByProject).map(([projectName, projectTasks]) => (

{projectName}

{projectTasks.map((task) => { const dueDate = task.dueDate ? new Date(task.dueDate) : null; const isOverdue = dueDate && dueDate < new Date(); return (
{task.headline}
{task.milestone && (
{task.milestone}
)}
{formatDate(task.dueDate)}
{isOverdue ? 'OVERDUE' : task.status.toUpperCase()}
); })}
))}
)} ); }