"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: number; dueDate: string | null; milestone: string | null; details: string | null; } interface ProjectTasks { projectName: string; tasks: Task[]; } export function Flow() { const [projectTasks, setProjectTasks] = 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)) { // Group tasks by project const groupedTasks = data.tasks.reduce((acc: { [key: string]: Task[] }, task: Task) => { if (!acc[task.projectName]) { acc[task.projectName] = []; } acc[task.projectName].push(task); return acc; }, {}); // Convert to array and sort tasks by due date within each project const sortedProjectTasks = Object.entries(groupedTasks as { [key: string]: Task[] }).map(([projectName, tasks]) => ({ projectName, tasks: tasks.sort((a: Task, b: Task) => { if (!a.dueDate) return 1; if (!b.dueDate) return -1; return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime(); }).slice(0, 6) // Limit to 6 tasks per project })); // Sort projects by earliest due date sortedProjectTasks.sort((a, b) => { const aEarliest = a.tasks[0]?.dueDate ? new Date(a.tasks[0].dueDate).getTime() : Infinity; const bEarliest = b.tasks[0]?.dueDate ? new Date(b.tasks[0].dueDate).getTime() : Infinity; return aEarliest - bEarliest; }); setProjectTasks(sortedProjectTasks); } else { setProjectTasks([]); } 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 formatDate = (dateString: string | null) => { if (!dateString) return 'No due date'; const date = new Date(dateString); return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' }); }; const getStatusInfo = (status: number): { label: string; className: string } => { switch (status) { case 1: return { label: 'NEW', className: 'bg-blue-100 text-blue-800' }; case 2: return { label: 'IN PROGRESS', className: 'bg-yellow-100 text-yellow-800' }; case 3: return { label: 'DONE', className: 'bg-green-100 text-green-800' }; case 4: return { label: 'BLOCKED', className: 'bg-red-100 text-red-800' }; case 0: default: return { label: 'UNKNOWN', className: 'bg-gray-100 text-gray-800' }; } }; return ( Tasks {loading ? (
) : error ? (
{error}
) : projectTasks.length === 0 ? (
No tasks found
) : (
{projectTasks.map((project) => (

{project.projectName}

{project.tasks.map((task) => { const statusInfo = getStatusInfo(task.status); return (
{task.headline} {task.milestone && ( {task.milestone} )}
{statusInfo.label} {formatDate(task.dueDate)}
); })}
))}
)} ); }