testfix/frontend/app/tasks/[id]/page.tsx

213 lines
6.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
import { ArrowLeft, Trash2, CheckCircle, Clock, Circle } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Task, fetchTask, 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 TaskDetail() {
const params = useParams();
const router = useRouter();
const [task, setTask] = useState<Task | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
useEffect(() => {
const loadTask = async () => {
try {
setLoading(true);
const id = parseInt(params.id as string);
const data = await fetchTask(id);
setTask(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load task");
} finally {
setLoading(false);
}
};
if (params.id) {
loadTask();
}
}, [params.id]);
const handleDelete = async () => {
if (!task) return;
try {
await deleteTask(task.id);
router.push("/");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete task");
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground">Loading task...</div>
</div>
);
}
if (error || !task) {
return (
<div className="max-w-2xl mx-auto">
<Link
href="/"
className="inline-flex items-center text-muted-foreground hover:text-foreground mb-6"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Dashboard
</Link>
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<p className="text-destructive mb-4">{error || "Task not found"}</p>
<Link href="/">
<Button variant="outline">Go to Dashboard</Button>
</Link>
</CardContent>
</Card>
</div>
);
}
const StatusIcon = statusIcons[task.status];
return (
<div className="max-w-2xl mx-auto">
<Link
href="/"
className="inline-flex items-center text-muted-foreground hover:text-foreground mb-6"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Dashboard
</Link>
<Card>
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<StatusIcon className="h-6 w-6 mt-1 text-muted-foreground" />
<div>
<CardTitle className="text-2xl">{task.title}</CardTitle>
<CardDescription className="mt-2">
Created {formatDate(task.createdAt)}
</CardDescription>
</div>
</div>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="text-destructive">
<Trash2 className="h-5 w-5" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Task</DialogTitle>
<DialogDescription>
Are you sure you want to delete this task? This action cannot
be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setDeleteDialogOpen(false)}
>
Cancel
</Button>
<Button variant="destructive" onClick={handleDelete}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex gap-2">
<Badge variant="outline" className={statusColors[task.status]}>
{task.status.replace("_", " ")}
</Badge>
<Badge variant="outline" className={priorityColors[task.priority]}>
{task.priority} priority
</Badge>
</div>
{task.description && (
<div>
<h3 className="text-sm font-medium text-muted-foreground mb-2">
Description
</h3>
<p className="text-foreground whitespace-pre-wrap">
{task.description}
</p>
</div>
)}
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-border">
<div>
<h3 className="text-sm font-medium text-muted-foreground mb-1">
Created
</h3>
<p className="text-foreground">{formatDate(task.createdAt)}</p>
</div>
<div>
<h3 className="text-sm font-medium text-muted-foreground mb-1">
Last Updated
</h3>
<p className="text-foreground">{formatDate(task.updatedAt)}</p>
</div>
</div>
</CardContent>
</Card>
</div>
);
}