testfix/frontend/app/page.tsx

217 lines
6.9 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { Plus, Trash2, CheckCircle, Clock, Circle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Task, fetchTasks, deleteTask } from "@/lib/api";
const statusIcons = {
pending: Circle,
in_progress: Clock,
completed: CheckCircle,
};
const statusColors = {
pending: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
in_progress: "bg-blue-500/20 text-blue-400 border-blue-500/30",
completed: "bg-green-500/20 text-green-400 border-green-500/30",
};
const priorityColors = {
low: "bg-slate-500/20 text-slate-400 border-slate-500/30",
medium: "bg-orange-500/20 text-orange-400 border-orange-500/30",
high: "bg-red-500/20 text-red-400 border-red-500/30",
};
export default function Dashboard() {
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadTasks = async () => {
try {
setLoading(true);
const data = await fetchTasks();
setTasks(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load tasks");
} finally {
setLoading(false);
}
};
useEffect(() => {
loadTasks();
}, []);
const handleDelete = async (id: number) => {
try {
await deleteTask(id);
setTasks(tasks.filter((t) => t.id !== id));
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete task");
}
};
const stats = {
total: tasks.length,
pending: tasks.filter((t) => t.status === "pending").length,
inProgress: tasks.filter((t) => t.status === "in_progress").length,
completed: tasks.filter((t) => t.status === "completed").length,
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground">Loading tasks...</div>
</div>
);
}
return (
<div className="space-y-8">
{/* Stats Overview */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Total Tasks
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">{stats.total}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Pending
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-yellow-400">
{stats.pending}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
In Progress
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-blue-400">
{stats.inProgress}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
Completed
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-green-400">
{stats.completed}
</div>
</CardContent>
</Card>
</div>
{/* Actions */}
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Tasks</h2>
<Link href="/tasks/new">
<Button>
<Plus className="mr-2 h-4 w-4" />
Add Task
</Button>
</Link>
</div>
{/* Error Message */}
{error && (
<div className="bg-destructive/20 text-destructive-foreground border border-destructive/30 rounded-lg p-4">
{error}
</div>
)}
{/* Task List */}
{tasks.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<p className="text-muted-foreground mb-4">No tasks yet</p>
<Link href="/tasks/new">
<Button>
<Plus className="mr-2 h-4 w-4" />
Create your first task
</Button>
</Link>
</CardContent>
</Card>
) : (
<div className="grid gap-4">
{tasks.map((task) => {
const StatusIcon = statusIcons[task.status];
return (
<Card
key={task.id}
className="hover:border-primary/50 transition-colors"
>
<CardContent className="p-4">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1">
<StatusIcon className="h-5 w-5 mt-0.5 text-muted-foreground" />
<div className="flex-1 min-w-0">
<Link
href={`/tasks/${task.id}`}
className="font-medium hover:text-primary transition-colors"
>
{task.title}
</Link>
{task.description && (
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
{task.description}
</p>
)}
<div className="flex gap-2 mt-2">
<Badge
variant="outline"
className={statusColors[task.status]}
>
{task.status.replace("_", " ")}
</Badge>
<Badge
variant="outline"
className={priorityColors[task.priority]}
>
{task.priority}
</Badge>
</div>
</div>
</div>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-destructive"
onClick={() => handleDelete(task.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
);
})}
</div>
)}
</div>
);
}