120 行
3.7 KiB
TypeScript
120 行
3.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { apiFetch } from "@/lib/api";
|
|
|
|
type Submission = {
|
|
id: number;
|
|
user_id: number;
|
|
problem_id: number;
|
|
contest_id: number | null;
|
|
status: string;
|
|
score: number;
|
|
time_ms: number;
|
|
created_at: number;
|
|
};
|
|
|
|
type ListResp = { items: Submission[]; page: number; page_size: number };
|
|
|
|
export default function SubmissionsPage() {
|
|
const [userId, setUserId] = useState("");
|
|
const [problemId, setProblemId] = useState("");
|
|
const [contestId, setContestId] = useState("");
|
|
const [items, setItems] = useState<Submission[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
const load = async () => {
|
|
setLoading(true);
|
|
setError("");
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (userId) params.set("user_id", userId);
|
|
if (problemId) params.set("problem_id", problemId);
|
|
if (contestId) params.set("contest_id", contestId);
|
|
const data = await apiFetch<ListResp>(`/api/v1/submissions?${params.toString()}`);
|
|
setItems(data.items);
|
|
} catch (e: unknown) {
|
|
setError(String(e));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
return (
|
|
<main className="mx-auto max-w-6xl px-6 py-8">
|
|
<h1 className="text-2xl font-semibold">提交记录</h1>
|
|
|
|
<div className="mt-4 grid gap-3 rounded-xl border bg-white p-4 md:grid-cols-4">
|
|
<input
|
|
className="rounded border px-3 py-2"
|
|
placeholder="user_id"
|
|
value={userId}
|
|
onChange={(e) => setUserId(e.target.value)}
|
|
/>
|
|
<input
|
|
className="rounded border px-3 py-2"
|
|
placeholder="problem_id"
|
|
value={problemId}
|
|
onChange={(e) => setProblemId(e.target.value)}
|
|
/>
|
|
<input
|
|
className="rounded border px-3 py-2"
|
|
placeholder="contest_id"
|
|
value={contestId}
|
|
onChange={(e) => setContestId(e.target.value)}
|
|
/>
|
|
<button
|
|
className="rounded bg-zinc-900 px-4 py-2 text-white disabled:opacity-50"
|
|
onClick={() => void load()}
|
|
disabled={loading}
|
|
>
|
|
{loading ? "加载中..." : "筛选"}
|
|
</button>
|
|
</div>
|
|
|
|
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
|
|
|
<div className="mt-4 overflow-x-auto rounded-xl border bg-white">
|
|
<table className="min-w-full text-sm">
|
|
<thead className="bg-zinc-100 text-left">
|
|
<tr>
|
|
<th className="px-3 py-2">ID</th>
|
|
<th className="px-3 py-2">用户</th>
|
|
<th className="px-3 py-2">题目</th>
|
|
<th className="px-3 py-2">状态</th>
|
|
<th className="px-3 py-2">分数</th>
|
|
<th className="px-3 py-2">耗时(ms)</th>
|
|
<th className="px-3 py-2">详情</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{items.map((s) => (
|
|
<tr key={s.id} className="border-t">
|
|
<td className="px-3 py-2">{s.id}</td>
|
|
<td className="px-3 py-2">{s.user_id}</td>
|
|
<td className="px-3 py-2">{s.problem_id}</td>
|
|
<td className="px-3 py-2">{s.status}</td>
|
|
<td className="px-3 py-2">{s.score}</td>
|
|
<td className="px-3 py-2">{s.time_ms}</td>
|
|
<td className="px-3 py-2">
|
|
<Link className="text-blue-600 underline" href={`/submissions/${s.id}`}>
|
|
查看
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|