judge4c/src/app/(protected)/dashboard/page.tsx

423 lines
12 KiB
TypeScript
Raw Normal View History

2025-06-21 15:19:49 +00:00
import {
Users,
BookOpen,
CheckCircle,
Clock,
TrendingUp,
2025-06-21 09:44:14 +00:00
AlertCircle,
BarChart3,
Target,
2025-06-21 15:19:49 +00:00
Activity,
GraduationCapIcon,
2025-06-21 09:44:14 +00:00
} from "lucide-react";
import Link from "next/link";
2025-06-21 15:19:49 +00:00
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import prisma from "@/lib/prisma";
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
2025-06-21 09:44:14 +00:00
interface Stats {
totalUsers?: number;
totalProblems?: number;
totalSubmissions?: number;
totalStudents?: number;
completedProblems?: number;
}
interface Activity {
type: string;
title: string;
description: string;
time: Date;
status?: string;
}
export default async function DashboardPage() {
const session = await auth();
const user = session?.user;
2025-06-21 15:19:49 +00:00
2025-06-21 09:44:14 +00:00
if (!user) {
redirect("/sign-in");
}
// 获取用户的完整信息
const fullUser = await prisma.user.findUnique({
where: { id: user.id },
2025-06-21 15:19:49 +00:00
select: { id: true, name: true, email: true, image: true, role: true },
2025-06-21 09:44:14 +00:00
});
if (!fullUser) {
redirect("/sign-in");
}
// 根据用户角色获取不同的统计数据
let stats: Stats = {};
let recentActivity: Activity[] = [];
if (fullUser.role === "ADMIN") {
// 管理员统计
2025-06-21 15:19:49 +00:00
const [totalUsers, totalProblems, totalSubmissions, recentUsers] =
await Promise.all([
prisma.user.count(),
prisma.problem.count(),
prisma.submission.count(),
prisma.user.findMany({
take: 5,
orderBy: { createdAt: "desc" },
select: {
id: true,
name: true,
email: true,
role: true,
createdAt: true,
},
}),
]);
2025-06-21 09:44:14 +00:00
stats = { totalUsers, totalProblems, totalSubmissions };
2025-06-21 15:19:49 +00:00
recentActivity = recentUsers.map((user) => ({
2025-06-21 09:44:14 +00:00
type: "新用户注册",
title: user.name || user.email,
description: `角色: ${user.role}`,
2025-06-21 15:19:49 +00:00
time: user.createdAt,
2025-06-21 09:44:14 +00:00
}));
} else if (fullUser.role === "TEACHER") {
// 教师统计
2025-06-21 15:19:49 +00:00
const [totalStudents, totalProblems, totalSubmissions, recentSubmissions] =
await Promise.all([
prisma.user.count({ where: { role: "GUEST" } }),
prisma.problem.count({ where: { isPublished: true } }),
prisma.submission.count(),
prisma.submission.findMany({
take: 5,
orderBy: { createdAt: "desc" },
include: {
user: { select: { name: true, email: true } },
problem: {
select: {
displayId: true,
localizations: {
where: { type: "TITLE", locale: "zh" },
select: { content: true },
},
},
},
},
}),
]);
2025-06-21 09:44:14 +00:00
stats = { totalStudents, totalProblems, totalSubmissions };
2025-06-21 15:19:49 +00:00
recentActivity = recentSubmissions.map((sub) => ({
2025-06-21 09:44:14 +00:00
type: "学生提交",
2025-06-21 15:19:49 +00:00
title: `${sub.user.name || sub.user.email} 提交了题目 ${
sub.problem.displayId
}`,
description:
sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
2025-06-21 09:44:14 +00:00
time: sub.createdAt,
2025-06-21 15:19:49 +00:00
status: sub.status,
2025-06-21 09:44:14 +00:00
}));
} else {
// 学生统计
2025-06-21 15:19:49 +00:00
const [
totalProblems,
completedProblems,
totalSubmissions,
recentSubmissions,
] = await Promise.all([
2025-06-21 09:44:14 +00:00
prisma.problem.count({ where: { isPublished: true } }),
2025-06-21 15:19:49 +00:00
prisma.submission.count({
where: {
userId: user.id,
status: "AC",
},
2025-06-21 09:44:14 +00:00
}),
prisma.submission.count({ where: { userId: user.id } }),
prisma.submission.findMany({
where: { userId: user.id },
take: 5,
orderBy: { createdAt: "desc" },
include: {
2025-06-21 15:19:49 +00:00
problem: {
select: {
2025-06-21 09:44:14 +00:00
displayId: true,
2025-06-21 15:19:49 +00:00
localizations: {
where: { type: "TITLE", locale: "zh" },
select: { content: true },
},
},
},
},
}),
2025-06-21 09:44:14 +00:00
]);
stats = { totalProblems, completedProblems, totalSubmissions };
2025-06-21 15:19:49 +00:00
recentActivity = recentSubmissions.map((sub) => ({
2025-06-21 09:44:14 +00:00
type: "我的提交",
title: `题目 ${sub.problem.displayId}`,
2025-06-21 15:19:49 +00:00
description:
sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
2025-06-21 09:44:14 +00:00
time: sub.createdAt,
2025-06-21 15:19:49 +00:00
status: sub.status,
2025-06-21 09:44:14 +00:00
}));
}
const getRoleConfig = () => {
switch (fullUser.role) {
case "ADMIN":
return {
title: "系统管理后台",
description: "管理整个系统的用户、题目和统计数据",
stats: [
2025-06-21 15:19:49 +00:00
{
label: "总用户数",
value: stats.totalUsers,
icon: Users,
color: "text-blue-600",
},
{
label: "总题目数",
value: stats.totalProblems,
icon: BookOpen,
color: "text-green-600",
},
{
label: "总提交数",
value: stats.totalSubmissions,
icon: Activity,
color: "text-purple-600",
},
2025-06-21 09:44:14 +00:00
],
actions: [
{
label: "管理员管理",
href: "/dashboard/management",
icon: Target,
},
2025-06-21 15:19:49 +00:00
{
label: "用户管理",
href: "/dashboard/usermanagement/guest",
icon: Users,
},
{
label: "教师管理",
href: "/dashboard/usermanagement/teacher",
icon: GraduationCapIcon,
},
2025-06-21 15:19:49 +00:00
{
label: "题目管理",
href: "/dashboard/usermanagement/problem",
icon: BookOpen,
},
],
2025-06-21 09:44:14 +00:00
};
case "TEACHER":
return {
title: "教师教学平台",
description: "查看学生学习情况,管理教学资源",
stats: [
2025-06-21 15:19:49 +00:00
{
label: "学生数量",
value: stats.totalStudents,
icon: Users,
color: "text-blue-600",
},
{
label: "题目数量",
value: stats.totalProblems,
icon: BookOpen,
color: "text-green-600",
},
{
label: "提交数量",
value: stats.totalSubmissions,
icon: Activity,
color: "text-purple-600",
},
2025-06-21 09:44:14 +00:00
],
actions: [
2025-06-21 15:19:49 +00:00
{
label: "用户管理",
2025-06-21 15:19:49 +00:00
href: "/dashboard/usermanagement/guest",
icon: Users,
},
{
label: "题目管理",
href: "/dashboard/usermanagement/problem",
icon: BookOpen,
},
{
label: "完成情况",
2025-06-21 15:19:49 +00:00
href: "/dashboard/teacher/dashboard",
icon: BarChart3,
},
],
2025-06-21 09:44:14 +00:00
};
default:
return {
title: "我的学习中心",
description: "继续您的编程学习之旅",
stats: [
2025-06-21 15:19:49 +00:00
{
label: "总题目数",
value: stats.totalProblems,
icon: BookOpen,
color: "text-blue-600",
},
{
label: "已完成",
value: stats.completedProblems,
icon: CheckCircle,
color: "text-green-600",
},
{
label: "提交次数",
value: stats.totalSubmissions,
icon: Activity,
color: "text-purple-600",
},
2025-06-21 09:44:14 +00:00
],
actions: [
2025-06-21 15:19:49 +00:00
{
label: "我的进度",
href: "/dashboard/student/dashboard",
icon: TrendingUp,
},
{ label: "开始做题", href: "/problemset", icon: BookOpen },
2025-06-21 15:19:49 +00:00
{ label: "个人设置", href: "/dashboard/management", icon: Target },
],
2025-06-21 09:44:14 +00:00
};
}
};
const config = getRoleConfig();
2025-06-21 15:19:49 +00:00
const completionRate =
fullUser.role === "GUEST"
? (stats.totalProblems || 0) > 0
? ((stats.completedProblems || 0) / (stats.totalProblems || 1)) * 100
: 0
: 0;
2025-06-21 09:44:14 +00:00
return (
<div className="space-y-6 p-6">
{/* 欢迎区域 */}
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">{config.title}</h1>
<p className="text-muted-foreground">{config.description}</p>
<div className="flex items-center gap-2">
<Badge variant="secondary">{fullUser.role}</Badge>
<span className="text-sm text-muted-foreground">
{fullUser.name || fullUser.email}
</span>
</div>
</div>
{/* 统计卡片 */}
<div className="grid gap-4 md:grid-cols-3">
{config.stats.map((stat, index) => (
<Card key={index}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
2025-06-21 15:19:49 +00:00
<CardTitle className="text-sm font-medium">
{stat.label}
</CardTitle>
2025-06-21 09:44:14 +00:00
<stat.icon className={`h-4 w-4 ${stat.color}`} />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stat.value}</div>
</CardContent>
</Card>
))}
</div>
{/* 学生进度条 */}
{fullUser.role === "GUEST" && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Target className="h-5 w-5" />
</CardTitle>
<CardDescription>
2025-06-21 15:19:49 +00:00
{stats.completedProblems || 0} / {stats.totalProblems || 0}{" "}
2025-06-21 09:44:14 +00:00
</CardDescription>
</CardHeader>
<CardContent>
<Progress value={completionRate} className="w-full" />
<p className="mt-2 text-sm text-muted-foreground">
: {completionRate.toFixed(1)}%
</p>
</CardContent>
</Card>
)}
{/* 快速操作 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>访</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-4">
2025-06-21 09:44:14 +00:00
{config.actions.map((action, index) => (
<Link key={index} href={action.href}>
<Button variant="outline" className="w-full justify-start">
<action.icon className="mr-2 h-4 w-4" />
{action.label}
</Button>
</Link>
))}
</div>
</CardContent>
</Card>
{/* 最近活动 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{recentActivity.length > 0 ? (
recentActivity.map((activity, index) => (
<div key={index} className="flex items-center space-x-4">
<div className="flex-shrink-0">
{activity.status === "AC" ? (
<CheckCircle className="h-5 w-5 text-green-500" />
) : activity.status ? (
<AlertCircle className="h-5 w-5 text-yellow-500" />
) : (
<Clock className="h-5 w-5 text-gray-500" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium">{activity.title}</p>
2025-06-21 15:19:49 +00:00
<p className="text-sm text-muted-foreground">
{activity.description}
</p>
2025-06-21 09:44:14 +00:00
</div>
<div className="text-sm text-muted-foreground">
{new Date(activity.time).toLocaleDateString()}
</div>
</div>
))
) : (
<p className="text-sm text-muted-foreground"></p>
)}
</div>
</CardContent>
</Card>
</div>
);
2025-06-20 12:18:13 +00:00
}