PMFlow/frontend/src/pages/skill-tasks/SkillTasksPage.tsx

138 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Link, useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { ArrowLeft, ListTodo } from "lucide-react";
import { getUserSkillTasks } from "@/api/skill";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { toZhPriority, toZhStatus } from "@/lib/display";
import { formatBeijingDateTime } from "@/lib/datetime";
export default function SkillTasksPage() {
const { id: userId, skillId } = useParams();
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["skill-tasks", userId, skillId],
queryFn: () => getUserSkillTasks(userId!, skillId!),
enabled: !!userId && !!skillId,
});
const items = data?.items ?? [];
const total = data?.total ?? items.length;
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center gap-4">
<Button variant="ghost" size="icon" asChild>
<Link to={userId ? `/users/${userId}/profile` : "/users"}>
<ArrowLeft className="h-4 w-4" />
</Link>
</Button>
<div>
<h1 className="text-2xl font-bold tracking-tight">技能历史任务</h1>
<p className="text-muted-foreground">
用户 {userId} / 技能 {skillId}
</p>
</div>
</div>
<Card>
<CardHeader>
<CardTitle>任务列表 ({total})</CardTitle>
</CardHeader>
<CardContent>
{isLoading ? (
<p className="text-muted-foreground">加载中…</p>
) : error ? (
<div className="space-y-3">
<p className="text-destructive">
加载失败:{(error as Error).message || "请求异常"}
</p>
<Button variant="outline" size="sm" onClick={() => refetch()}>
重试
</Button>
</div>
) : items.length === 0 ? (
<div className="flex flex-col items-center py-10 text-center text-muted-foreground">
<ListTodo className="mb-3 h-10 w-10 opacity-60" />
<p>暂无历史任务</p>
<p className="mt-1 text-xs">
当前后端可能尚未提供技能历史任务接口,页面已兼容空数据。
</p>
</div>
) : (
<>
<div className="hidden overflow-x-auto [-webkit-overflow-scrolling:touch] md:block">
<Table className="min-w-[720px]">
<TableHeader>
<TableRow>
<TableHead className="min-w-[240px] whitespace-nowrap">任务</TableHead>
<TableHead className="min-w-[90px] whitespace-nowrap">状态</TableHead>
<TableHead className="min-w-[90px] whitespace-nowrap">优先级</TableHead>
<TableHead className="min-w-[90px] whitespace-nowrap text-right">工时 (h)</TableHead>
<TableHead className="min-w-[170px] whitespace-nowrap">更新时间</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{items.map((task) => (
<TableRow key={task.id}>
<TableCell className="min-w-[240px]">
<div>
<p className="font-medium">{task.title}</p>
{task.description && (
<p className="line-clamp-1 text-xs text-muted-foreground">
{task.description}
</p>
)}
</div>
</TableCell>
<TableCell className="min-w-[90px] whitespace-nowrap">{toZhStatus(task.status)}</TableCell>
<TableCell className="min-w-[90px] whitespace-nowrap">{toZhPriority(task.priority)}</TableCell>
<TableCell className="min-w-[90px] whitespace-nowrap text-right">
{typeof task.estimated_hours === "number"
? task.estimated_hours.toFixed(1)
: "-"}
</TableCell>
<TableCell className="min-w-[170px] whitespace-nowrap">
{task.updated_at
? formatBeijingDateTime(task.updated_at)
: "-"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="space-y-3 md:hidden">
{items.map((task) => (
<article key={task.id} className="rounded-lg border bg-white p-4">
<p className="text-base font-semibold">{task.title}</p>
{task.description && (
<p className="mt-1 line-clamp-2 text-sm text-muted-foreground">
{task.description}
</p>
)}
<div className="mt-3 flex items-center justify-between text-sm">
<span>状态:{toZhStatus(task.status)}</span>
<span>优先级:{toZhPriority(task.priority)}</span>
</div>
<div className="mt-2 flex items-center justify-between text-xs text-muted-foreground">
<span>工时:{typeof task.estimated_hours === "number" ? task.estimated_hours.toFixed(1) : "-"}h</span>
<span>{task.updated_at ? formatBeijingDateTime(task.updated_at) : "-"}</span>
</div>
</article>
))}
</div>
</>
)}
</CardContent>
</Card>
</div>
);
}