working leantime widget 35
This commit is contained in:
parent
df4c03944e
commit
cda5d7322d
@ -5,55 +5,53 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { RefreshCw } from "lucide-react";
|
import { RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
interface StatusLabel {
|
interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
headline: string;
|
||||||
statusType: string;
|
projectName: string;
|
||||||
class: string;
|
projectId: number;
|
||||||
}
|
status: string;
|
||||||
|
dueDate: string | null;
|
||||||
interface Project {
|
milestone: string | null;
|
||||||
id: string;
|
details: string | null;
|
||||||
name: string;
|
|
||||||
labels: StatusLabel[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Flow() {
|
export function Flow() {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [tasks, setTasks] = useState<Task[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const [retryTimeout, setRetryTimeout] = useState<NodeJS.Timeout | null>(null);
|
const [retryTimeout, setRetryTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const fetchProjects = async (isRefresh = false) => {
|
const fetchTasks = async (isRefresh = false) => {
|
||||||
try {
|
try {
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
}
|
}
|
||||||
const response = await fetch('/api/leantime/status-labels');
|
const response = await fetch('/api/leantime/tasks');
|
||||||
|
|
||||||
if (response.status === 429) {
|
if (response.status === 429) {
|
||||||
const retryAfter = parseInt(response.headers.get('retry-after') || '60');
|
const retryAfter = parseInt(response.headers.get('retry-after') || '60');
|
||||||
const timeout = setTimeout(() => fetchProjects(), retryAfter * 1000);
|
const timeout = setTimeout(() => fetchTasks(), retryAfter * 1000);
|
||||||
setRetryTimeout(timeout);
|
setRetryTimeout(timeout);
|
||||||
setError(`Rate limit exceeded. Retrying in ${retryAfter} seconds...`);
|
setError(`Rate limit exceeded. Retrying in ${retryAfter} seconds...`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch projects');
|
throw new Error('Failed to fetch tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.projects && Array.isArray(data.projects)) {
|
if (data.tasks && Array.isArray(data.tasks)) {
|
||||||
setProjects(data.projects);
|
setTasks(data.tasks);
|
||||||
} else {
|
} else {
|
||||||
setProjects([]);
|
setTasks([]);
|
||||||
}
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching projects:', err);
|
console.error('Error fetching tasks:', err);
|
||||||
setError('Failed to fetch projects');
|
setError('Failed to fetch tasks');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
@ -61,7 +59,7 @@ export function Flow() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProjects();
|
fetchTasks();
|
||||||
return () => {
|
return () => {
|
||||||
if (retryTimeout) {
|
if (retryTimeout) {
|
||||||
clearTimeout(retryTimeout);
|
clearTimeout(retryTimeout);
|
||||||
@ -72,11 +70,11 @@ export function Flow() {
|
|||||||
return (
|
return (
|
||||||
<Card className="transition-transform duration-500 ease-in-out transform hover:scale-105">
|
<Card className="transition-transform duration-500 ease-in-out transform hover:scale-105">
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-lg font-medium">Flow</CardTitle>
|
<CardTitle className="text-lg font-medium">Tasks</CardTitle>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => fetchProjects(true)}
|
onClick={() => fetchTasks(true)}
|
||||||
disabled={refreshing || !!retryTimeout}
|
disabled={refreshing || !!retryTimeout}
|
||||||
className={refreshing ? 'animate-spin' : ''}
|
className={refreshing ? 'animate-spin' : ''}
|
||||||
>
|
>
|
||||||
@ -90,27 +88,26 @@ export function Flow() {
|
|||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="text-center text-sm text-red-500">{error}</div>
|
<div className="text-center text-sm text-red-500">{error}</div>
|
||||||
) : projects.length === 0 ? (
|
) : tasks.length === 0 ? (
|
||||||
<div className="text-center text-sm text-gray-500">No projects found</div>
|
<div className="text-center text-sm text-gray-500">No tasks found</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{projects.map((project) => (
|
{tasks.map((task) => (
|
||||||
<div key={project.id} className="space-y-2">
|
<div key={task.id} className="space-y-2">
|
||||||
<h3 className="font-medium text-gray-900">{project.name}</h3>
|
<h3 className="font-medium text-gray-900">{task.projectName}</h3>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{project.labels.map((label) => (
|
<div className="flex items-center justify-between p-2 rounded-lg bg-white shadow-sm">
|
||||||
<div
|
<span className="text-sm font-medium text-gray-700">
|
||||||
key={label.id}
|
{task.headline}
|
||||||
className="flex items-center justify-between p-2 rounded-lg bg-white shadow-sm"
|
</span>
|
||||||
>
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
<span className="text-sm font-medium text-gray-700">
|
task.status === 'NEW' ? 'bg-blue-100 text-blue-800' :
|
||||||
{label.name}
|
task.status === 'INPROGRESS' ? 'bg-yellow-100 text-yellow-800' :
|
||||||
</span>
|
'bg-green-100 text-green-800'
|
||||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${label.class}`}>
|
}`}>
|
||||||
{label.statusType}
|
{task.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user