"use client"; import React, { useEffect, useState } from "react"; import { apiFetch, type RatingHistoryItem } from "@/lib/api"; import { readToken } from "@/lib/auth"; import { useI18nText } from "@/lib/i18n"; import { ChevronDown, ChevronUp, RefreshCw, Save, Shield, Users, } from "lucide-react"; type AdminUser = { id: number; username: string; rating: number; created_at: number; total_submissions: number; total_ac: number; }; type ListResp = { items: AdminUser[]; total_count: number; page: number; page_size: number; }; type SubmissionRow = { id: number; problem_id: number; status: string; score: number; language: string; created_at: number; }; type RedeemRow = { id: number; item_name: string; quantity: number; day_type: string; total_cost: number; created_at: number; }; function fmtTs(v: number): string { if (!v) return "-"; return new Date(v * 1000).toLocaleString(); } function DetailPanel({ userId, tx }: { userId: number; tx: (zh: string, en: string) => string }) { const [tab, setTab] = useState<"subs" | "rating" | "redeem">("subs"); const [subs, setSubs] = useState([]); const [ratingH, setRatingH] = useState([]); const [redeems, setRedeems] = useState([]); const [loading, setLoading] = useState(false); const token = readToken() ?? ""; useEffect(() => { setLoading(true); const loadTab = async () => { try { if (tab === "subs") { const d = await apiFetch<{ items: SubmissionRow[] }>( `/api/v1/submissions?user_id=${userId}&page=1&page_size=50`, undefined, token ); setSubs(d.items ?? []); } else if (tab === "rating") { const d = await apiFetch( `/api/v1/admin/users/${userId}/rating-history?limit=100`, undefined, token ); setRatingH(Array.isArray(d) ? d : []); } else { const d = await apiFetch( `/api/v1/admin/redeem-records?user_id=${userId}&limit=100`, undefined, token ); setRedeems(Array.isArray(d) ? d : []); } } catch { /* ignore */ } setLoading(false); }; void loadTab(); }, [tab, userId, token]); const tabCls = (t: string) => `px-3 py-1 text-xs border ${tab === t ? "bg-zinc-900 text-white" : "bg-white text-zinc-700 hover:bg-zinc-100"}`; return (
{loading &&

{tx("加载中...", "Loading...")}

} {!loading && tab === "subs" && (
{subs.map((s) => ( ))} {subs.length === 0 && }
ID{tx("题目", "Problem")} {tx("状态", "Status")}{tx("分数", "Score")} {tx("时间", "Time")}
{s.id} P{s.problem_id} {s.status} {s.score} {fmtTs(s.created_at)}
{tx("无记录", "No records")}
)} {!loading && tab === "rating" && (
{ratingH.map((item, i) => (
0 ? "text-emerald-600" : "text-red-600"}`}> {item.change > 0 ? `+${item.change}` : item.change} {item.note} {fmtTs(item.created_at)}
))} {ratingH.length === 0 &&

{tx("无记录", "No records")}

}
)} {!loading && tab === "redeem" && (
{redeems.map((r) => ( ))} {redeems.length === 0 && }
{tx("物品", "Item")}{tx("数量", "Qty")} {tx("花费", "Cost")}{tx("时间", "Time")}
{r.item_name} {r.quantity} -{r.total_cost} {fmtTs(r.created_at)}
{tx("无记录", "No records")}
)}
); } export default function AdminUsersPage() { const { tx } = useI18nText(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [msg, setMsg] = useState(""); const [expandedId, setExpandedId] = useState(null); const load = async () => { setLoading(true); setError(""); try { const token = readToken(); if (!token) throw new Error(tx("请先登录管理员账号", "Please sign in with admin account first")); const data = await apiFetch("/api/v1/admin/users?page=1&page_size=200", undefined, token); setItems(data.items ?? []); } catch (e: unknown) { setError(String(e)); } finally { setLoading(false); } }; useEffect(() => { void load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const updateRating = async (userId: number, rating: number) => { setMsg(""); setError(""); try { const token = readToken(); if (!token) throw new Error(tx("请先登录管理员账号", "Please sign in with admin account first")); await apiFetch( `/api/v1/admin/users/${userId}/rating`, { method: "PATCH", body: JSON.stringify({ rating }), }, token ); setMsg(tx(`已更新用户 ${userId} Rating=${rating}`, `Updated user ${userId} rating=${rating}`)); await load(); } catch (e: unknown) { setError(String(e)); } }; return (

{tx("管理员用户与积分", "Admin Users & Rating")}

{tx("默认管理员账号:", "Default admin account: ")} admin / whoami139

{msg &&

{msg}

} {error &&

{error}

}
{items.map((user) => ( {expandedId === user.id && ( )} ))} {!loading && items.length === 0 && ( )}
ID {tx("用户名", "Username")} Rating {tx("提交", "Subs")} AC {tx("创建时间", "Created At")} {tx("操作", "Action")}
{user.id} {user.username} { const value = Number(e.target.value); setItems((prev) => prev.map((row) => (row.id === user.id ? { ...row, rating: value } : row)) ); }} /> {user.total_submissions} {user.total_ac} {fmtTs(user.created_at)}
{tx("暂无用户数据", "No users found")}
{items.map((user) => (

#{user.id} · {user.username}

{tx("创建时间:", "Created: ")} {fmtTs(user.created_at)} {" | "} {tx("提交:", "Subs: ")}{user.total_submissions} {" | AC: "}{user.total_ac}

Rating { const value = Number(e.target.value); setItems((prev) => prev.map((row) => (row.id === user.id ? { ...row, rating: value } : row)) ); }} />
{expandedId === user.id && }
))} {!loading && items.length === 0 && (

{tx("暂无用户数据", "No users found")}

)}
); }