From 8473d6ed611ec9d351eea6b6086bc581757c0c59 Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Tue, 19 May 2026 16:49:09 +0800 Subject: [PATCH] feat(problem-management): add localized problem title column in user table --- .../components/generic-page.tsx | 26 +++++++++++- .../user-management/components/user-table.tsx | 40 +++++++++++++------ .../user-management/config/problem.ts | 9 ++++- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/features/user-management/components/generic-page.tsx b/src/features/user-management/components/generic-page.tsx index 79f029b..6f6e5bb 100644 --- a/src/features/user-management/components/generic-page.tsx +++ b/src/features/user-management/components/generic-page.tsx @@ -2,7 +2,8 @@ import prisma from "@/lib/prisma"; import { UserTable } from "./user-table"; import { Role } from "@/generated/client"; import { UserConfig } from "./user-table"; -import type { User, Problem } from "@/generated/client"; +import type { User } from "@/generated/client"; +import { getLocale } from "next-intl/server"; interface GenericPageProps { resourceType: "admin" | "teacher" | "student" | "problem"; @@ -14,7 +15,28 @@ export default async function GenericPage({ config, }: GenericPageProps) { if (resourceType === "problem") { - const data: Problem[] = await prisma.problem.findMany({}); + const locale = await getLocale(); + const problems = await prisma.problem.findMany({ + select: { + id: true, + displayId: true, + difficulty: true, + localizations: { + where: { type: "TITLE", locale: locale === "en" ? "en" : "zh" }, + select: { content: true }, + take: 1, + }, + }, + orderBy: { displayId: "asc" }, + }); + + const data = problems.map((problem) => ({ + id: problem.id, + displayId: problem.displayId, + difficulty: problem.difficulty, + title: problem.localizations[0]?.content ?? "-", + })); + return ; } else { const role = resourceType.toUpperCase() as Role; diff --git a/src/features/user-management/components/user-table.tsx b/src/features/user-management/components/user-table.tsx index fbe24c3..f47396b 100644 --- a/src/features/user-management/components/user-table.tsx +++ b/src/features/user-management/components/user-table.tsx @@ -65,6 +65,7 @@ import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useTranslations } from "next-intl"; import { Difficulty, Role } from "@/generated/client"; import type { User, Problem } from "@/generated/client"; import { @@ -110,7 +111,11 @@ export interface UserConfig { type UserTableProps = | { config: UserConfig; data: User[] } - | { config: UserConfig; data: Problem[] }; + | { config: UserConfig; data: ProblemRow[] }; + +type ProblemRow = Pick & { + title: string; +}; type UserForm = { id?: string; @@ -154,13 +159,14 @@ const addProblemSchema = z.object({ }); export function UserTable(props: UserTableProps) { + const tDifficulty = useTranslations("Difficulty"); const isProblem = props.config.resourceType === "problem"; const router = useRouter(); - const problemData = isProblem ? (props.data as Problem[]) : undefined; + const problemData = isProblem ? (props.data as ProblemRow[]) : undefined; const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); - const [editingUser, setEditingUser] = useState(null); + const [editingUser, setEditingUser] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteBatch, setDeleteBatch] = useState(false); const [rowSelection, setRowSelection] = useState({}); @@ -174,15 +180,15 @@ export function UserTable(props: UserTableProps) { const [pageInput, setPageInput] = useState(pagination.pageIndex + 1); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [pendingDeleteItem, setPendingDeleteItem] = useState< - User | Problem | null + User | ProblemRow | null >(null); useEffect(() => { setPageInput(pagination.pageIndex + 1); }, [pagination.pageIndex]); // 表格列 - const tableColumns = React.useMemo[]>(() => { - const columns: ColumnDef[] = [ + const tableColumns = React.useMemo[]>(() => { + const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( @@ -206,13 +212,20 @@ export function UserTable(props: UserTableProps) { }, ]; props.config.columns.forEach((col) => { - const column: ColumnDef = { + const column: ColumnDef = { accessorKey: col.key, header: col.label, cell: ({ row }) => { // 类型安全分流 if (col.key === "displayId" && isProblem) { - return (row.original as Problem).displayId; + return (row.original as ProblemRow).displayId; + } + if (col.key === "title" && isProblem) { + return (row.original as ProblemRow).title; + } + if (col.key === "difficulty" && isProblem) { + const difficulty = String(row.getValue(col.key)) as Difficulty; + return tDifficulty(difficulty); } if (col.key === "createdAt" || col.key === "updatedAt") { const value = row.getValue(col.key); @@ -250,7 +263,7 @@ export function UserTable(props: UserTableProps) { onClick={() => { if (isProblem) { // 如果是problem类型,跳转到编辑路由,使用displayId - const problem = item as Problem; + const problem = item as ProblemRow; router.push(`/dashboard/admin/problems/${problem.id}/edit`); } else { // 如果是用户类型,打开编辑弹窗 @@ -280,7 +293,7 @@ export function UserTable(props: UserTableProps) { }, }); return columns; - }, [props.config, router, isProblem]); + }, [props.config, router, isProblem, tDifficulty]); const table = useReactTable({ data: props.data, @@ -761,7 +774,7 @@ export function UserTable(props: UserTableProps) { } // 用ref保证获取最新data - const dataRef = React.useRef(props.data); + const dataRef = React.useRef(props.data); React.useEffect(() => { dataRef.current = props.data; }, [props.data]); @@ -798,6 +811,7 @@ export function UserTable(props: UserTableProps) { createdAt: "创建时间", actions: "操作", displayId: "题目编号", + title: "题目标题", difficulty: "难度", }; return ( @@ -1065,7 +1079,7 @@ export function UserTable(props: UserTableProps) { table.getFilteredSelectedRowModel().rows; for (const row of selectedRows) { if (isProblem) { - await deleteProblem((row.original as Problem).id); + await deleteProblem((row.original as ProblemRow).id); } else { await deleteUser( props.config.resourceType as @@ -1110,7 +1124,7 @@ export function UserTable(props: UserTableProps) { onClick={async () => { if (pendingDeleteItem) { if (isProblem) { - await deleteProblem((pendingDeleteItem as Problem).id); + await deleteProblem((pendingDeleteItem as ProblemRow).id); } else { await deleteUser( props.config.resourceType as "admin" | "teacher" | "student", diff --git a/src/features/user-management/config/problem.ts b/src/features/user-management/config/problem.ts index 71eec9d..9254a43 100644 --- a/src/features/user-management/config/problem.ts +++ b/src/features/user-management/config/problem.ts @@ -3,6 +3,7 @@ import { z } from "zod"; export const problemSchema = z.object({ id: z.string(), displayId: z.number(), + title: z.string().optional(), difficulty: z.string(), createdAt: z.string(), }); @@ -23,7 +24,6 @@ export const problemConfig = { title: "题目列表", apiPath: "/api/problem", columns: [ - { key: "id", label: "ID", sortable: true }, { key: "displayId", label: "题目编号", @@ -31,6 +31,13 @@ export const problemConfig = { searchable: true, placeholder: "搜索编号", }, + { + key: "title", + label: "题目标题", + sortable: true, + searchable: true, + placeholder: "搜索标题", + }, { key: "difficulty", label: "难度",