Initial MVP for QA asset backend

This commit is contained in:
2026-07-05 17:44:15 +08:00
commit 95b5e09fd4
71 changed files with 6546 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
"use client";
import Link from "next/link";
import { FileText, Play, RotateCw } from "lucide-react";
import { api, formatDateTime } from "@/lib/api";
import type { FeishuSession, TaskRun } from "@/lib/types";
import StatusBadge from "./StatusBadge";
export default function SessionTable({ sessions, onChanged }: { sessions: FeishuSession[]; onChanged: () => void }) {
async function run(id: number, reprocess = false) {
await api.post<TaskRun>(`/sessions/${id}/${reprocess ? "reprocess" : "process"}`, {});
onChanged();
}
return (
<div className="table-wrap rounded-md border border-line bg-white">
<table className="min-w-[1100px] w-full border-collapse text-left text-sm">
<thead className="bg-panel text-xs uppercase text-slate-500">
<tr>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
<th className="px-4 py-3"></th>
</tr>
</thead>
<tbody>
{sessions.map((session) => (
<tr key={session.id} className="border-t border-line align-top">
<td className="px-4 py-3">{session.date ?? "-"}</td>
<td className="px-4 py-3 font-medium text-ink">{session.title}</td>
<td className="px-4 py-3">{session.teachers ?? "-"}</td>
<td className="px-4 py-3">
{session.feishu_doc_url ? (
<span className="text-jade">{session.feishu_doc_url}</span>
) : (
<span className="text-slate-400">-</span>
)}
</td>
<td className="px-4 py-3">
<StatusBadge status={session.process_status} />
</td>
<td className="px-4 py-3">{session.qa_count}</td>
<td className="px-4 py-3">{session.pending_review_count}</td>
<td className="px-4 py-3">{session.standard_qa_count}</td>
<td className="max-w-xs px-4 py-3 text-rose-700">{session.failed_reason ?? "-"}</td>
<td className="px-4 py-3">
<div className="flex flex-wrap gap-2">
<Link
href={`/sessions/${session.id}`}
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
>
<FileText className="h-3.5 w-3.5" aria-hidden />
</Link>
<button
onClick={() => run(session.id)}
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
>
<Play className="h-3.5 w-3.5" aria-hidden />
</button>
<button
onClick={() => run(session.id, true)}
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
>
<RotateCw className="h-3.5 w-3.5" aria-hidden />
</button>
</div>
</td>
</tr>
))}
{sessions.length === 0 ? (
<tr>
<td className="px-4 py-8 text-center text-slate-500" colSpan={10}>
</td>
</tr>
) : null}
</tbody>
</table>
</div>
);
}