mirror of
https://github.com/massbug/judge4c.git
synced 2026-05-20 13:18:52 +00:00
feat(problem-management): add localized problem title column in user table
This commit is contained in:
parent
318249d20e
commit
8473d6ed61
@ -2,7 +2,8 @@ import prisma from "@/lib/prisma";
|
|||||||
import { UserTable } from "./user-table";
|
import { UserTable } from "./user-table";
|
||||||
import { Role } from "@/generated/client";
|
import { Role } from "@/generated/client";
|
||||||
import { UserConfig } from "./user-table";
|
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 {
|
interface GenericPageProps {
|
||||||
resourceType: "admin" | "teacher" | "student" | "problem";
|
resourceType: "admin" | "teacher" | "student" | "problem";
|
||||||
@ -14,7 +15,28 @@ export default async function GenericPage({
|
|||||||
config,
|
config,
|
||||||
}: GenericPageProps) {
|
}: GenericPageProps) {
|
||||||
if (resourceType === "problem") {
|
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 <UserTable config={config} data={data} />;
|
return <UserTable config={config} data={data} />;
|
||||||
} else {
|
} else {
|
||||||
const role = resourceType.toUpperCase() as Role;
|
const role = resourceType.toUpperCase() as Role;
|
||||||
|
|||||||
@ -65,6 +65,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Difficulty, Role } from "@/generated/client";
|
import { Difficulty, Role } from "@/generated/client";
|
||||||
import type { User, Problem } from "@/generated/client";
|
import type { User, Problem } from "@/generated/client";
|
||||||
import {
|
import {
|
||||||
@ -110,7 +111,11 @@ export interface UserConfig {
|
|||||||
|
|
||||||
type UserTableProps =
|
type UserTableProps =
|
||||||
| { config: UserConfig; data: User[] }
|
| { config: UserConfig; data: User[] }
|
||||||
| { config: UserConfig; data: Problem[] };
|
| { config: UserConfig; data: ProblemRow[] };
|
||||||
|
|
||||||
|
type ProblemRow = Pick<Problem, "id" | "displayId" | "difficulty"> & {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
type UserForm = {
|
type UserForm = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -154,13 +159,14 @@ const addProblemSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function UserTable(props: UserTableProps) {
|
export function UserTable(props: UserTableProps) {
|
||||||
|
const tDifficulty = useTranslations("Difficulty");
|
||||||
const isProblem = props.config.resourceType === "problem";
|
const isProblem = props.config.resourceType === "problem";
|
||||||
const router = useRouter();
|
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 [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
||||||
const [editingUser, setEditingUser] = useState<User | Problem | null>(null);
|
const [editingUser, setEditingUser] = useState<User | ProblemRow | null>(null);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [deleteBatch, setDeleteBatch] = useState(false);
|
const [deleteBatch, setDeleteBatch] = useState(false);
|
||||||
const [rowSelection, setRowSelection] = useState({});
|
const [rowSelection, setRowSelection] = useState({});
|
||||||
@ -174,15 +180,15 @@ export function UserTable(props: UserTableProps) {
|
|||||||
const [pageInput, setPageInput] = useState(pagination.pageIndex + 1);
|
const [pageInput, setPageInput] = useState(pagination.pageIndex + 1);
|
||||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||||
const [pendingDeleteItem, setPendingDeleteItem] = useState<
|
const [pendingDeleteItem, setPendingDeleteItem] = useState<
|
||||||
User | Problem | null
|
User | ProblemRow | null
|
||||||
>(null);
|
>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageInput(pagination.pageIndex + 1);
|
setPageInput(pagination.pageIndex + 1);
|
||||||
}, [pagination.pageIndex]);
|
}, [pagination.pageIndex]);
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
const tableColumns = React.useMemo<ColumnDef<User | Problem>[]>(() => {
|
const tableColumns = React.useMemo<ColumnDef<User | ProblemRow>[]>(() => {
|
||||||
const columns: ColumnDef<User | Problem>[] = [
|
const columns: ColumnDef<User | ProblemRow>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@ -206,13 +212,20 @@ export function UserTable(props: UserTableProps) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
props.config.columns.forEach((col) => {
|
props.config.columns.forEach((col) => {
|
||||||
const column: ColumnDef<User | Problem> = {
|
const column: ColumnDef<User | ProblemRow> = {
|
||||||
accessorKey: col.key,
|
accessorKey: col.key,
|
||||||
header: col.label,
|
header: col.label,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
// 类型安全分流
|
// 类型安全分流
|
||||||
if (col.key === "displayId" && isProblem) {
|
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") {
|
if (col.key === "createdAt" || col.key === "updatedAt") {
|
||||||
const value = row.getValue(col.key);
|
const value = row.getValue(col.key);
|
||||||
@ -250,7 +263,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isProblem) {
|
if (isProblem) {
|
||||||
// 如果是problem类型,跳转到编辑路由,使用displayId
|
// 如果是problem类型,跳转到编辑路由,使用displayId
|
||||||
const problem = item as Problem;
|
const problem = item as ProblemRow;
|
||||||
router.push(`/dashboard/admin/problems/${problem.id}/edit`);
|
router.push(`/dashboard/admin/problems/${problem.id}/edit`);
|
||||||
} else {
|
} else {
|
||||||
// 如果是用户类型,打开编辑弹窗
|
// 如果是用户类型,打开编辑弹窗
|
||||||
@ -280,7 +293,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return columns;
|
return columns;
|
||||||
}, [props.config, router, isProblem]);
|
}, [props.config, router, isProblem, tDifficulty]);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: props.data,
|
data: props.data,
|
||||||
@ -761,7 +774,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 用ref保证获取最新data
|
// 用ref保证获取最新data
|
||||||
const dataRef = React.useRef<User[] | Problem[]>(props.data);
|
const dataRef = React.useRef<User[] | ProblemRow[]>(props.data);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
dataRef.current = props.data;
|
dataRef.current = props.data;
|
||||||
}, [props.data]);
|
}, [props.data]);
|
||||||
@ -798,6 +811,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
createdAt: "创建时间",
|
createdAt: "创建时间",
|
||||||
actions: "操作",
|
actions: "操作",
|
||||||
displayId: "题目编号",
|
displayId: "题目编号",
|
||||||
|
title: "题目标题",
|
||||||
difficulty: "难度",
|
difficulty: "难度",
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@ -1065,7 +1079,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
table.getFilteredSelectedRowModel().rows;
|
table.getFilteredSelectedRowModel().rows;
|
||||||
for (const row of selectedRows) {
|
for (const row of selectedRows) {
|
||||||
if (isProblem) {
|
if (isProblem) {
|
||||||
await deleteProblem((row.original as Problem).id);
|
await deleteProblem((row.original as ProblemRow).id);
|
||||||
} else {
|
} else {
|
||||||
await deleteUser(
|
await deleteUser(
|
||||||
props.config.resourceType as
|
props.config.resourceType as
|
||||||
@ -1110,7 +1124,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (pendingDeleteItem) {
|
if (pendingDeleteItem) {
|
||||||
if (isProblem) {
|
if (isProblem) {
|
||||||
await deleteProblem((pendingDeleteItem as Problem).id);
|
await deleteProblem((pendingDeleteItem as ProblemRow).id);
|
||||||
} else {
|
} else {
|
||||||
await deleteUser(
|
await deleteUser(
|
||||||
props.config.resourceType as "admin" | "teacher" | "student",
|
props.config.resourceType as "admin" | "teacher" | "student",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
export const problemSchema = z.object({
|
export const problemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
displayId: z.number(),
|
displayId: z.number(),
|
||||||
|
title: z.string().optional(),
|
||||||
difficulty: z.string(),
|
difficulty: z.string(),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
});
|
});
|
||||||
@ -23,7 +24,6 @@ export const problemConfig = {
|
|||||||
title: "题目列表",
|
title: "题目列表",
|
||||||
apiPath: "/api/problem",
|
apiPath: "/api/problem",
|
||||||
columns: [
|
columns: [
|
||||||
{ key: "id", label: "ID", sortable: true },
|
|
||||||
{
|
{
|
||||||
key: "displayId",
|
key: "displayId",
|
||||||
label: "题目编号",
|
label: "题目编号",
|
||||||
@ -31,6 +31,13 @@ export const problemConfig = {
|
|||||||
searchable: true,
|
searchable: true,
|
||||||
placeholder: "搜索编号",
|
placeholder: "搜索编号",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "title",
|
||||||
|
label: "题目标题",
|
||||||
|
sortable: true,
|
||||||
|
searchable: true,
|
||||||
|
placeholder: "搜索标题",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "difficulty",
|
key: "difficulty",
|
||||||
label: "难度",
|
label: "难度",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user