NeahFront7/components/flow.tsx
2025-04-12 14:07:30 +02:00

149 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;
status: string;
dueDate: string | null;
milestone: string | null;
}
export function Flow() {
const [tasks, setTasks] = useState<Task[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [retryTimeout, setRetryTimeout] = useState<NodeJS.Timeout | null>(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)) {
setTasks(data.tasks);
} 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 'done':
return 'bg-green-100 text-green-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'
});
};
return (
<Card className="transition-transform duration-500 ease-in-out transform hover:scale-105">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-lg font-medium">Flow</CardTitle>
<Button
variant="ghost"
size="icon"
onClick={() => fetchTasks(true)}
disabled={refreshing || !!retryTimeout}
className={refreshing ? 'animate-spin' : ''}
>
<RefreshCw className="h-4 w-4" />
</Button>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex items-center justify-center py-4">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
) : error ? (
<div className="text-center text-sm text-red-500">{error}</div>
) : tasks.length === 0 ? (
<div className="text-center text-sm text-gray-500">No tasks found</div>
) : (
<div className="space-y-6">
{tasks.map((task) => (
<div key={task.id} className="border rounded-lg p-4 bg-white shadow-sm">
<div className="flex items-start justify-between">
<div className="space-y-1">
<div className="text-sm font-medium text-gray-600">{task.projectName}</div>
<div className="text-base font-medium text-blue-600 hover:text-blue-800">
{task.headline}
</div>
<div className="flex items-center space-x-3 text-sm text-gray-500">
<div className="flex items-center space-x-1">
<span className="w-4 h-4">🗓</span>
<span>{formatDate(task.dueDate)}</span>
</div>
{task.milestone && (
<div className="flex items-center space-x-1">
<span className="px-2 py-1 rounded-full bg-gray-100">
{task.milestone}
</span>
</div>
)}
</div>
</div>
<div className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusClass(task.status)}`}>
{task.status.toUpperCase()}
</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}