+
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/management/page.tsx b/src/app/(protected)/dashboard/management/page.tsx
index 7bdf039..5545c71 100644
--- a/src/app/(protected)/dashboard/management/page.tsx
+++ b/src/app/(protected)/dashboard/management/page.tsx
@@ -1,58 +1,50 @@
-"use client"
-import React, { useState } from "react"
-import { Separator } from "@/components/ui/separator"
-import ProfilePage from "./profile/page"
-import ChangePasswordPage from "./change-password/page"
+"use client";
+
+import { cn } from "@/lib/utils";
+import React, { useState } from "react";
+import ProfilePage from "./profile/page";
+import { Button } from "@/components/ui/button";
+import ChangePasswordPage from "./change-password/page";
export default function ManagementDefaultPage() {
- const [activePage, setActivePage] = useState("profile")
+ const [activePage, setActivePage] = useState("profile");
const renderContent = () => {
switch (activePage) {
case "profile":
- return
+ return
;
case "change-password":
- return
+ return
;
default:
- return
+ return
;
}
- }
+ };
return (
{/* 顶部导航栏 */}
-
-
{/* 页面切换按钮 */}
-
-
{/* 主体内容 */}
-
- {renderContent()}
-
+
{renderContent()}
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/app/(protected)/dashboard/management/profile/page.tsx b/src/app/(protected)/dashboard/management/profile/page.tsx
index a789024..9d6e319 100644
--- a/src/app/(protected)/dashboard/management/profile/page.tsx
+++ b/src/app/(protected)/dashboard/management/profile/page.tsx
@@ -1,7 +1,8 @@
-// src/app/(app)/management/profile/page.tsx
"use client";
import { useEffect, useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
import { getUserInfo } from "@/app/(protected)/dashboard/management/actions/getUserInfo";
import { updateUserInfo } from "@/app/(protected)/dashboard/management/actions/updateUserInfo";
@@ -34,33 +35,38 @@ export default function ProfilePage() {
}, []);
const handleSave = async () => {
- const nameInput = document.getElementById("name") as HTMLInputElement | null;
- const emailInput = document.getElementById("email") as HTMLInputElement | null;
+ const nameInput = document.getElementById(
+ "name"
+ ) as HTMLInputElement | null;
+ const emailInput = document.getElementById(
+ "email"
+ ) as HTMLInputElement | null;
- if (!nameInput || !emailInput) {
- alert("表单元素缺失");
- return;
- }
+ if (!nameInput || !emailInput) {
+ alert("表单元素缺失");
+ return;
+ }
- const formData = new FormData();
- formData.append("name", nameInput.value);
- formData.append("email", emailInput.value);
+ const formData = new FormData();
+ formData.append("name", nameInput.value);
+ formData.append("email", emailInput.value);
- try {
- const updatedUser = await updateUserInfo(formData);
- setUser(updatedUser);
- setIsEditing(false);
- } catch (error: unknown) {
- const errorMessage = error instanceof Error ? error.message : '更新用户信息失败';
- alert(errorMessage);
- }
-};
+ try {
+ const updatedUser = await updateUserInfo(formData);
+ setUser(updatedUser);
+ setIsEditing(false);
+ } catch (error: unknown) {
+ const errorMessage =
+ error instanceof Error ? error.message : "更新用户信息失败";
+ alert(errorMessage);
+ }
+ };
if (!user) return
加载中...
;
return (
-
+
用户信息
@@ -71,82 +77,94 @@ export default function ProfilePage() {
{isEditing ? (
-
) : (
-
{user?.name || "未提供"}
+
+ {user?.name || "未提供"}
+
)}
-
角色:{user?.role}
-
邮箱验证时间:{user.emailVerified ? new Date(user.emailVerified).toLocaleString() : "未验证"}
+
角色:{user?.role}
+
+ 邮箱验证时间:
+ {user.emailVerified
+ ? new Date(user.emailVerified).toLocaleString()
+ : "未验证"}
+
-
+
-
-
{user.id}
+
+
{user.id}
-
+
{isEditing ? (
-
) : (
-
{user.email}
+
{user.email}
)}
-
-
{new Date(user.createdAt).toLocaleString()}
+
+
+ {new Date(user.createdAt).toLocaleString()}
+
-
-
{new Date(user.updatedAt).toLocaleString()}
+
+
+ {new Date(user.updatedAt).toLocaleString()}
+
{isEditing ? (
<>
- setIsEditing(false)}
type="button"
- className="px-4 py-2 bg-gray-300 rounded-md hover:bg-gray-400 transition-colors"
+ className="px-4 py-2 rounded-md transition-colors"
>
取消
-
-
+
保存
-
+
>
) : (
- setIsEditing(true)}
type="button"
- className="px-4 py-2 bg-black text-white rounded-md hover:bg-gray-800 transition-colors"
+ className="px-4 py-2 rounded-md transition-colors"
>
编辑信息
-
+
)}
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx
index 16d0921..397287a 100644
--- a/src/app/(protected)/dashboard/page.tsx
+++ b/src/app/(protected)/dashboard/page.tsx
@@ -1,22 +1,28 @@
-import { auth } from "@/lib/auth";
-import prisma from "@/lib/prisma";
-import { redirect } from "next/navigation";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { Progress } from "@/components/ui/progress";
-import {
- Users,
- BookOpen,
- CheckCircle,
- Clock,
- TrendingUp,
+import {
+ Users,
+ BookOpen,
+ CheckCircle,
+ Clock,
+ TrendingUp,
AlertCircle,
BarChart3,
Target,
- Activity
+ Activity,
} from "lucide-react";
import Link from "next/link";
+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";
interface Stats {
totalUsers?: number;
@@ -37,7 +43,7 @@ interface Activity {
export default async function DashboardPage() {
const session = await auth();
const user = session?.user;
-
+
if (!user) {
redirect("/sign-in");
}
@@ -45,7 +51,7 @@ export default async function DashboardPage() {
// 获取用户的完整信息
const fullUser = await prisma.user.findUnique({
where: { id: user.id },
- select: { id: true, name: true, email: true, image: true, role: true }
+ select: { id: true, name: true, email: true, image: true, role: true },
});
if (!fullUser) {
@@ -58,62 +64,81 @@ export default async function DashboardPage() {
if (fullUser.role === "ADMIN") {
// 管理员统计
- 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 }
- })
- ]);
+ 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,
+ },
+ }),
+ ]);
stats = { totalUsers, totalProblems, totalSubmissions };
- recentActivity = recentUsers.map(user => ({
+ recentActivity = recentUsers.map((user) => ({
type: "新用户注册",
title: user.name || user.email,
description: `角色: ${user.role}`,
- time: user.createdAt
+ time: user.createdAt,
}));
} else if (fullUser.role === "TEACHER") {
// 教师统计
- 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 } }
- }
- }
- }
- })
- ]);
+ 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 },
+ },
+ },
+ },
+ },
+ }),
+ ]);
stats = { totalStudents, totalProblems, totalSubmissions };
- recentActivity = recentSubmissions.map(sub => ({
+ recentActivity = recentSubmissions.map((sub) => ({
type: "学生提交",
- title: `${sub.user.name || sub.user.email} 提交了题目 ${sub.problem.displayId}`,
- description: sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
+ title: `${sub.user.name || sub.user.email} 提交了题目 ${
+ sub.problem.displayId
+ }`,
+ description:
+ sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
time: sub.createdAt,
- status: sub.status
+ status: sub.status,
}));
} else {
// 学生统计
- const [totalProblems, completedProblems, totalSubmissions, recentSubmissions] = await Promise.all([
+ const [
+ totalProblems,
+ completedProblems,
+ totalSubmissions,
+ recentSubmissions,
+ ] = await Promise.all([
prisma.problem.count({ where: { isPublished: true } }),
- prisma.submission.count({
- where: {
- userId: user.id,
- status: "AC"
- }
+ prisma.submission.count({
+ where: {
+ userId: user.id,
+ status: "AC",
+ },
}),
prisma.submission.count({ where: { userId: user.id } }),
prisma.submission.findMany({
@@ -121,23 +146,27 @@ export default async function DashboardPage() {
take: 5,
orderBy: { createdAt: "desc" },
include: {
- problem: {
- select: {
+ problem: {
+ select: {
displayId: true,
- localizations: { where: { type: "TITLE", locale: "zh" }, select: { content: true } }
- }
- }
- }
- })
+ localizations: {
+ where: { type: "TITLE", locale: "zh" },
+ select: { content: true },
+ },
+ },
+ },
+ },
+ }),
]);
stats = { totalProblems, completedProblems, totalSubmissions };
- recentActivity = recentSubmissions.map(sub => ({
+ recentActivity = recentSubmissions.map((sub) => ({
type: "我的提交",
title: `题目 ${sub.problem.displayId}`,
- description: sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
+ description:
+ sub.problem.localizations[0]?.content || `题目${sub.problem.displayId}`,
time: sub.createdAt,
- status: sub.status
+ status: sub.status,
}));
}
@@ -148,52 +177,129 @@ export default async function DashboardPage() {
title: "系统管理后台",
description: "管理整个系统的用户、题目和统计数据",
stats: [
- { 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" }
+ {
+ 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",
+ },
],
actions: [
- { label: "用户管理", href: "/dashboard/usermanagement/guest", icon: Users },
- { label: "题目管理", href: "/dashboard/usermanagement/problem", icon: BookOpen },
- { label: "管理员设置", href: "/dashboard/management", icon: Target }
- ]
+ {
+ label: "用户管理",
+ href: "/dashboard/usermanagement/guest",
+ icon: Users,
+ },
+ {
+ label: "题目管理",
+ href: "/dashboard/usermanagement/problem",
+ icon: BookOpen,
+ },
+ {
+ label: "管理员设置",
+ href: "/dashboard/management",
+ icon: Target,
+ },
+ ],
};
case "TEACHER":
return {
title: "教师教学平台",
description: "查看学生学习情况,管理教学资源",
stats: [
- { 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" }
+ {
+ 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",
+ },
],
actions: [
- { label: "学生管理", href: "/dashboard/usermanagement/guest", icon: Users },
- { label: "题目管理", href: "/dashboard/usermanagement/problem", icon: BookOpen },
- { label: "统计分析", href: "/dashboard/teacher/dashboard", icon: BarChart3 }
- ]
+ {
+ label: "学生管理",
+ href: "/dashboard/usermanagement/guest",
+ icon: Users,
+ },
+ {
+ label: "题目管理",
+ href: "/dashboard/usermanagement/problem",
+ icon: BookOpen,
+ },
+ {
+ label: "统计分析",
+ href: "/dashboard/teacher/dashboard",
+ icon: BarChart3,
+ },
+ ],
};
default:
return {
title: "我的学习中心",
description: "继续您的编程学习之旅",
stats: [
- { 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" }
+ {
+ 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",
+ },
],
actions: [
{ label: "开始做题", href: "/problemset", icon: BookOpen },
- { label: "我的进度", href: "/dashboard/student/dashboard", icon: TrendingUp },
- { label: "个人设置", href: "/dashboard/management", icon: Target }
- ]
+ {
+ label: "我的进度",
+ href: "/dashboard/student/dashboard",
+ icon: TrendingUp,
+ },
+ { label: "个人设置", href: "/dashboard/management", icon: Target },
+ ],
};
}
};
const config = getRoleConfig();
- const completionRate = fullUser.role === "GUEST" ?
- ((stats.totalProblems || 0) > 0 ? ((stats.completedProblems || 0) / (stats.totalProblems || 1)) * 100 : 0) : 0;
+ const completionRate =
+ fullUser.role === "GUEST"
+ ? (stats.totalProblems || 0) > 0
+ ? ((stats.completedProblems || 0) / (stats.totalProblems || 1)) * 100
+ : 0
+ : 0;
return (
@@ -214,7 +320,9 @@ export default async function DashboardPage() {
{config.stats.map((stat, index) => (
- {stat.label}
+
+ {stat.label}
+
@@ -233,7 +341,8 @@ export default async function DashboardPage() {
学习进度
- 已完成 {stats.completedProblems || 0} / {stats.totalProblems || 0} 道题目
+ 已完成 {stats.completedProblems || 0} / {stats.totalProblems || 0}{" "}
+ 道题目
@@ -287,7 +396,9 @@ export default async function DashboardPage() {
{activity.title}
-
{activity.description}
+
+ {activity.description}
+
{new Date(activity.time).toLocaleDateString()}
diff --git a/src/app/(protected)/dashboard/(userdashboard)/student/dashboard/page.tsx b/src/app/(protected)/dashboard/student/dashboard/page.tsx
similarity index 73%
rename from src/app/(protected)/dashboard/(userdashboard)/student/dashboard/page.tsx
rename to src/app/(protected)/dashboard/student/dashboard/page.tsx
index d2d9c53..2db7818 100644
--- a/src/app/(protected)/dashboard/(userdashboard)/student/dashboard/page.tsx
+++ b/src/app/(protected)/dashboard/student/dashboard/page.tsx
@@ -1,7 +1,5 @@
"use client";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Progress } from "@/components/ui/progress";
import {
Table,
TableBody,
@@ -10,9 +8,11 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
-import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
import { useEffect, useState } from "react";
-import { getStudentDashboardData } from "@/app/(protected)/dashboard/(userdashboard)/_actions/student-dashboard";
+import { Progress } from "@/components/ui/progress";
+import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { getStudentDashboardData } from "@/app/(protected)/dashboard/actions/student-dashboard";
interface DashboardData {
completionData: {
@@ -86,13 +86,19 @@ export default function StudentDashboard() {
);
}
- const { completionData, errorData, difficultProblems, pieChartData, errorPieChartData } = data;
+ const {
+ completionData,
+ errorData,
+ difficultProblems,
+ pieChartData,
+ errorPieChartData,
+ } = data;
const COLORS = ["#4CAF50", "#FFC107"];
return (
学生仪表板
-
+
{/* 题目完成比例模块 */}
@@ -102,8 +108,12 @@ export default function StudentDashboard() {
- 已完成题目:{completionData.completed}/{completionData.total}
- {completionData.percentage}%
+
+ 已完成题目:{completionData.completed}/{completionData.total}
+
+
+ {completionData.percentage}%
+
@@ -119,9 +129,17 @@ export default function StudentDashboard() {
paddingAngle={5}
dataKey="value"
>
- {pieChartData.map((entry: { name: string; value: number }, index: number) => (
-
|
- ))}
+ {pieChartData.map(
+ (
+ entry: { name: string; value: number },
+ index: number
+ ) => (
+
|
+ )
+ )}
@@ -138,7 +156,9 @@ export default function StudentDashboard() {
- 错题数量:{errorData.wrong}/{errorData.total}
+
+ 错题数量:{errorData.wrong}/{errorData.total}
+
{errorData.percentage}%
@@ -155,9 +175,17 @@ export default function StudentDashboard() {
paddingAngle={5}
dataKey="value"
>
- {errorPieChartData.map((entry: { name: string; value: number }, index: number) => (
-
|
- ))}
+ {errorPieChartData.map(
+ (
+ entry: { name: string; value: number },
+ index: number
+ ) => (
+
|
+ )
+ )}
@@ -188,14 +216,21 @@ export default function StudentDashboard() {
- {difficultProblems.map((problem: { id: string | number; title: string; difficulty: string; errorCount: number }) => (
-
- {problem.id}
- {problem.title}
- {problem.difficulty}
- {problem.errorCount}
-
- ))}
+ {difficultProblems.map(
+ (problem: {
+ id: string | number;
+ title: string;
+ difficulty: string;
+ errorCount: number;
+ }) => (
+
+ {problem.id}
+ {problem.title}
+ {problem.difficulty}
+ {problem.errorCount}
+
+ )
+ )}
) : (
@@ -208,4 +243,4 @@ export default function StudentDashboard() {
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/page.tsx b/src/app/(protected)/dashboard/teacher/dashboard/page.tsx
similarity index 80%
rename from src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/page.tsx
rename to src/app/(protected)/dashboard/teacher/dashboard/page.tsx
index 01f0ba1..407fa2c 100644
--- a/src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/page.tsx
+++ b/src/app/(protected)/dashboard/teacher/dashboard/page.tsx
@@ -1,10 +1,13 @@
"use client";
-import { TrendingUp } from "lucide-react";
-import { Bar, BarChart, XAxis, YAxis, LabelList, CartesianGrid } from "recharts";
-import { Button } from "@/components/ui/button";
-import { useState, useEffect } from "react";
-
+import {
+ Bar,
+ BarChart,
+ XAxis,
+ YAxis,
+ LabelList,
+ CartesianGrid,
+} from "recharts";
import {
Card,
CardContent,
@@ -27,7 +30,14 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
-import { getDashboardStats, ProblemCompletionData, DifficultProblemData } from "@/app/(protected)/dashboard/(userdashboard)/_actions/teacher-dashboard";
+import { TrendingUp } from "lucide-react";
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ getDashboardStats,
+ ProblemCompletionData,
+ DifficultProblemData,
+} from "@/app/(protected)/dashboard/actions/teacher-dashboard";
const ITEMS_PER_PAGE = 5; // 每页显示的题目数量
@@ -45,7 +55,9 @@ const chartConfig = {
export default function TeacherDashboard() {
const [currentPage, setCurrentPage] = useState(1);
const [chartData, setChartData] = useState([]);
- const [difficultProblems, setDifficultProblems] = useState([]);
+ const [difficultProblems, setDifficultProblems] = useState<
+ DifficultProblemData[]
+ >([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -57,8 +69,8 @@ export default function TeacherDashboard() {
setChartData(data.problemData);
setDifficultProblems(data.difficultProblems);
} catch (err) {
- setError(err instanceof Error ? err.message : '获取数据失败');
- console.error('Failed to fetch dashboard data:', err);
+ setError(err instanceof Error ? err.message : "获取数据失败");
+ console.error("Failed to fetch dashboard data:", err);
} finally {
setLoading(false);
}
@@ -68,7 +80,7 @@ export default function TeacherDashboard() {
}, []);
const totalPages = Math.ceil(chartData.length / ITEMS_PER_PAGE);
-
+
// 获取当前页的数据
const currentPageData = chartData.slice(
(currentPage - 1) * ITEMS_PER_PAGE,
@@ -128,9 +140,9 @@ export default function TeacherDashboard() {
barCategoryGap={20}
>
- `${value}%`}
/>
}
/>
-
- `${value}人`}
+ `${value}人`}
/>
-
- `${value}人`}
+ `${value}人`}
/>
@@ -178,7 +190,9 @@ export default function TeacherDashboard() {
setCurrentPage(prev => Math.max(prev - 1, 1))}
+ onClick={() =>
+ setCurrentPage((prev) => Math.max(prev - 1, 1))
+ }
disabled={currentPage === 1}
>
上一页
@@ -189,7 +203,9 @@ export default function TeacherDashboard() {
setCurrentPage(prev => Math.min(prev + 1, totalPages))}
+ onClick={() =>
+ setCurrentPage((prev) => Math.min(prev + 1, totalPages))
+ }
disabled={currentPage === totalPages}
>
下一页
@@ -222,7 +238,9 @@ export default function TeacherDashboard() {
{difficultProblems.length === 0 ? (
) : (
@@ -236,7 +254,10 @@ export default function TeacherDashboard() {
{difficultProblems.map((problem) => (
- {problem.problemDisplayId || problem.id.substring(0, 8)}
+
+ {problem.problemDisplayId ||
+ problem.id.substring(0, 8)}
+
{problem.problemTitle}
{problem.problemCount}
@@ -250,4 +271,4 @@ export default function TeacherDashboard() {
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/test-data.tsx b/src/app/(protected)/dashboard/teacher/dashboard/test-data.tsx
similarity index 81%
rename from src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/test-data.tsx
rename to src/app/(protected)/dashboard/teacher/dashboard/test-data.tsx
index bf518d5..bee5f21 100644
--- a/src/app/(protected)/dashboard/(userdashboard)/teacher/dashboard/test-data.tsx
+++ b/src/app/(protected)/dashboard/teacher/dashboard/test-data.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
-import { getDashboardStats } from "@/app/(protected)/dashboard/(userdashboard)/_actions/teacher-dashboard";
+import { getDashboardStats } from "@/app/(protected)/dashboard/actions/teacher-dashboard";
interface DashboardData {
problemData: Array<{
@@ -37,8 +37,8 @@ export default function TestDataPage() {
const result = await getDashboardStats();
setData(result);
} catch (err) {
- setError(err instanceof Error ? err.message : '获取数据失败');
- console.error('Failed to fetch data:', err);
+ setError(err instanceof Error ? err.message : "获取数据失败");
+ console.error("Failed to fetch data:", err);
} finally {
setLoading(false);
}
@@ -58,7 +58,7 @@ export default function TestDataPage() {
return (
数据测试页面
-
+
题目完成数据
@@ -77,13 +77,17 @@ export default function TestDataPage() {
统计信息
- {JSON.stringify({
- totalProblems: data?.totalProblems,
- totalDifficultProblems: data?.totalDifficultProblems,
- }, null, 2)}
+ {JSON.stringify(
+ {
+ totalProblems: data?.totalProblems,
+ totalDifficultProblems: data?.totalDifficultProblems,
+ },
+ null,
+ 2
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/_actions/problemActions.ts b/src/app/(protected)/dashboard/usermanagement/_actions/problemActions.ts
deleted file mode 100644
index ca7ca81..0000000
--- a/src/app/(protected)/dashboard/usermanagement/_actions/problemActions.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-'use server'
-import prisma from '@/lib/prisma'
-import { revalidatePath } from 'next/cache'
-import type { Problem } from '@/generated/client'
-
-export async function createProblem(data: Omit
) {
- await prisma.problem.create({ data })
- revalidatePath('/usermanagement/problem')
-}
-
-export async function deleteProblem(id: string) {
- await prisma.problem.delete({ where: { id } })
- revalidatePath('/usermanagement/problem')
-}
\ No newline at end of file
diff --git a/src/app/(protected)/dashboard/usermanagement/_actions/userActions.ts b/src/app/(protected)/dashboard/usermanagement/_actions/userActions.ts
deleted file mode 100644
index e49269f..0000000
--- a/src/app/(protected)/dashboard/usermanagement/_actions/userActions.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-'use server'
-import prisma from '@/lib/prisma'
-import { revalidatePath } from 'next/cache'
-import bcrypt from 'bcryptjs'
-import type { User } from '@/generated/client'
-import { Role } from '@/generated/client'
-
-type UserType = 'admin' | 'teacher' | 'guest'
-
-export async function createUser(
- userType: UserType,
- data: Omit & { password?: string }
-) {
- let password = data.password
- if (password) {
- password = await bcrypt.hash(password, 10)
- }
-
- const role = userType.toUpperCase() as Role
- await prisma.user.create({ data: { ...data, password, role } })
- revalidatePath(`/usermanagement/${userType}`)
-}
-
-export async function updateUser(
- userType: UserType,
- id: string,
- data: Partial>
-) {
- const updateData = { ...data }
-
- // 如果包含密码字段且不为空,则进行加密
- if (data.password && data.password.trim() !== '') {
- updateData.password = await bcrypt.hash(data.password, 10)
- } else {
- // 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
- delete updateData.password
- }
-
- await prisma.user.update({ where: { id }, data: updateData })
- revalidatePath(`/usermanagement/${userType}`)
-}
-
-export async function deleteUser(userType: UserType, id: string) {
- await prisma.user.delete({ where: { id } })
- revalidatePath(`/usermanagement/${userType}`)
-}
\ No newline at end of file
diff --git a/src/app/(protected)/dashboard/usermanagement/_components/GenericLayout.tsx b/src/app/(protected)/dashboard/usermanagement/_components/GenericLayout.tsx
deleted file mode 100644
index a182e77..0000000
--- a/src/app/(protected)/dashboard/usermanagement/_components/GenericLayout.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import ProtectedLayout from "./ProtectedLayout";
-
-interface GenericLayoutProps {
- children: React.ReactNode;
- allowedRoles: string[];
-}
-
-export default function GenericLayout({ children, allowedRoles }: GenericLayoutProps) {
- return {children};
-}
\ No newline at end of file
diff --git a/src/app/(protected)/dashboard/usermanagement/actions/problemActions.ts b/src/app/(protected)/dashboard/usermanagement/actions/problemActions.ts
new file mode 100644
index 0000000..f86502a
--- /dev/null
+++ b/src/app/(protected)/dashboard/usermanagement/actions/problemActions.ts
@@ -0,0 +1,17 @@
+"use server";
+
+import prisma from "@/lib/prisma";
+import { revalidatePath } from "next/cache";
+import type { Problem } from "@/generated/client";
+
+export async function createProblem(
+ data: Omit
+) {
+ await prisma.problem.create({ data });
+ revalidatePath("/usermanagement/problem");
+}
+
+export async function deleteProblem(id: string) {
+ await prisma.problem.delete({ where: { id } });
+ revalidatePath("/usermanagement/problem");
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts b/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts
new file mode 100644
index 0000000..16aa684
--- /dev/null
+++ b/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts
@@ -0,0 +1,47 @@
+"use server";
+
+import bcrypt from "bcryptjs";
+import prisma from "@/lib/prisma";
+import { Role } from "@/generated/client";
+import { revalidatePath } from "next/cache";
+import type { User } from "@/generated/client";
+
+type UserType = "admin" | "teacher" | "guest";
+
+export async function createUser(
+ userType: UserType,
+ data: Omit & { password?: string }
+) {
+ let password = data.password;
+ if (password) {
+ password = await bcrypt.hash(password, 10);
+ }
+
+ const role = userType.toUpperCase() as Role;
+ await prisma.user.create({ data: { ...data, password, role } });
+ revalidatePath(`/usermanagement/${userType}`);
+}
+
+export async function updateUser(
+ userType: UserType,
+ id: string,
+ data: Partial>
+) {
+ const updateData = { ...data };
+
+ // 如果包含密码字段且不为空,则进行加密
+ if (data.password && data.password.trim() !== "") {
+ updateData.password = await bcrypt.hash(data.password, 10);
+ } else {
+ // 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
+ delete updateData.password;
+ }
+
+ await prisma.user.update({ where: { id }, data: updateData });
+ revalidatePath(`/usermanagement/${userType}`);
+}
+
+export async function deleteUser(userType: UserType, id: string) {
+ await prisma.user.delete({ where: { id } });
+ revalidatePath(`/usermanagement/${userType}`);
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/admin/layout.tsx b/src/app/(protected)/dashboard/usermanagement/admin/layout.tsx
index db50dae..711abf1 100644
--- a/src/app/(protected)/dashboard/usermanagement/admin/layout.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/admin/layout.tsx
@@ -1,5 +1,9 @@
-import GenericLayout from "../_components/GenericLayout";
+import GenericLayout from "../components/GenericLayout";
-export default function AdminLayout({ children }: { children: React.ReactNode }) {
+export default function AdminLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
return {children};
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/admin/page.tsx b/src/app/(protected)/dashboard/usermanagement/admin/page.tsx
index d911127..96065e8 100644
--- a/src/app/(protected)/dashboard/usermanagement/admin/page.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/admin/page.tsx
@@ -1,6 +1,6 @@
-import GenericPage from '@/features/user-management/components/generic-page'
-import { adminConfig } from '@/features/user-management/config/admin'
+import { adminConfig } from "@/features/user-management/config/admin";
+import GenericPage from "@/features/user-management/components/generic-page";
export default function AdminPage() {
- return
-}
\ No newline at end of file
+ return ;
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/components/GenericLayout.tsx b/src/app/(protected)/dashboard/usermanagement/components/GenericLayout.tsx
new file mode 100644
index 0000000..27a6163
--- /dev/null
+++ b/src/app/(protected)/dashboard/usermanagement/components/GenericLayout.tsx
@@ -0,0 +1,15 @@
+import ProtectedLayout from "./ProtectedLayout";
+
+interface GenericLayoutProps {
+ children: React.ReactNode;
+ allowedRoles: string[];
+}
+
+export default function GenericLayout({
+ children,
+ allowedRoles,
+}: GenericLayoutProps) {
+ return (
+ {children}
+ );
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/_components/ProtectedLayout.tsx b/src/app/(protected)/dashboard/usermanagement/components/ProtectedLayout.tsx
similarity index 80%
rename from src/app/(protected)/dashboard/usermanagement/_components/ProtectedLayout.tsx
rename to src/app/(protected)/dashboard/usermanagement/components/ProtectedLayout.tsx
index ed7830a..b3843f1 100644
--- a/src/app/(protected)/dashboard/usermanagement/_components/ProtectedLayout.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/components/ProtectedLayout.tsx
@@ -1,5 +1,5 @@
-import { auth } from "@/lib/auth";
import prisma from "@/lib/prisma";
+import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
interface ProtectedLayoutProps {
@@ -7,7 +7,10 @@ interface ProtectedLayoutProps {
allowedRoles: string[];
}
-export default async function ProtectedLayout({ children, allowedRoles }: ProtectedLayoutProps) {
+export default async function ProtectedLayout({
+ children,
+ allowedRoles,
+}: ProtectedLayoutProps) {
const session = await auth();
const userId = session?.user?.id;
@@ -17,7 +20,7 @@ export default async function ProtectedLayout({ children, allowedRoles }: Protec
const user = await prisma.user.findUnique({
where: { id: userId },
- select: { role: true }
+ select: { role: true },
});
if (!user || !allowedRoles.includes(user.role)) {
@@ -25,4 +28,4 @@ export default async function ProtectedLayout({ children, allowedRoles }: Protec
}
return {children}
;
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx b/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx
index 6c084f4..1c4b421 100644
--- a/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx
@@ -1,5 +1,13 @@
-import GenericLayout from "../_components/GenericLayout";
+import GenericLayout from "../components/GenericLayout";
-export default function GuestLayout({ children }: { children: React.ReactNode }) {
- return {children};
-}
\ No newline at end of file
+export default function GuestLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/guest/page.tsx b/src/app/(protected)/dashboard/usermanagement/guest/page.tsx
index 5d8d1bc..3aa6a30 100644
--- a/src/app/(protected)/dashboard/usermanagement/guest/page.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/guest/page.tsx
@@ -1,6 +1,6 @@
-import GenericPage from '@/features/user-management/components/generic-page'
-import { guestConfig } from '@/features/user-management/config/guest'
+import { guestConfig } from "@/features/user-management/config/guest";
+import GenericPage from "@/features/user-management/components/generic-page";
export default function GuestPage() {
- return
-}
\ No newline at end of file
+ return ;
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/problem/layout.tsx b/src/app/(protected)/dashboard/usermanagement/problem/layout.tsx
index b4a002e..5bd5c5a 100644
--- a/src/app/(protected)/dashboard/usermanagement/problem/layout.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/problem/layout.tsx
@@ -1,5 +1,13 @@
-import GenericLayout from "../_components/GenericLayout";
+import GenericLayout from "../components/GenericLayout";
-export default function ProblemLayout({ children }: { children: React.ReactNode }) {
- return {children};
-}
\ No newline at end of file
+export default function ProblemLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/problem/page.tsx b/src/app/(protected)/dashboard/usermanagement/problem/page.tsx
index e67a3d0..d61ac67 100644
--- a/src/app/(protected)/dashboard/usermanagement/problem/page.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/problem/page.tsx
@@ -1,6 +1,6 @@
-import GenericPage from '@/features/user-management/components/generic-page'
-import { problemConfig } from '@/features/user-management/config/problem'
+import { problemConfig } from "@/features/user-management/config/problem";
+import GenericPage from "@/features/user-management/components/generic-page";
export default function ProblemPage() {
- return
-}
\ No newline at end of file
+ return ;
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/teacher/layout.tsx b/src/app/(protected)/dashboard/usermanagement/teacher/layout.tsx
index 8051801..53d57cb 100644
--- a/src/app/(protected)/dashboard/usermanagement/teacher/layout.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/teacher/layout.tsx
@@ -1,5 +1,9 @@
-import GenericLayout from "../_components/GenericLayout";
+import GenericLayout from "../components/GenericLayout";
-export default function TeacherLayout({ children }: { children: React.ReactNode }) {
+export default function TeacherLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
return {children};
-}
\ No newline at end of file
+}
diff --git a/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx b/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx
index 9bccf6f..26393b9 100644
--- a/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx
+++ b/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx
@@ -1,6 +1,6 @@
-import GenericPage from '@/features/user-management/components/generic-page'
-import { teacherConfig } from '@/features/user-management/config/teacher'
+import { teacherConfig } from "@/features/user-management/config/teacher";
+import GenericPage from "@/features/user-management/components/generic-page";
export default function TeacherPage() {
- return
-}
\ No newline at end of file
+ return ;
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index a257927..26923bb 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -33,13 +33,13 @@
--chart-5: 213 16% 16%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar-foreground: 213 13% 6%;
+ --sidebar-primary: 213 13% 16%;
+ --sidebar-primary-foreground: 213 13% 76%;
+ --sidebar-accent: 0 0% 85%;
+ --sidebar-accent-foreground: 0 0% 25%;
+ --sidebar-border: 0 0% 95%;
+ --sidebar-ring: 213 13% 16%;
}
.dark {
@@ -67,14 +67,14 @@
--chart-3: 216 28% 22%;
--chart-4: 210 7% 28%;
--chart-5: 210 20% 82%;
- --sidebar-background: 240 5.9% 10%;
- --sidebar-foreground: 240 4.8% 95.9%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
- --sidebar-accent: 240 3.7% 15.9%;
- --sidebar-accent-foreground: 240 4.8% 95.9%;
- --sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar-background: 216 28% 5%;
+ --sidebar-foreground: 210 17% 92%;
+ --sidebar-primary: 210 17% 82%;
+ --sidebar-primary-foreground: 210 17% 22%;
+ --sidebar-accent: 216 28% 22%;
+ --sidebar-accent-foreground: 216 28% 82%;
+ --sidebar-border: 216 18% 12%;
+ --sidebar-ring: 210 17% 82%;
}
}
@@ -119,14 +119,3 @@ code[data-theme*=" "] span {
color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
}
-
-
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 7726dce..d50795c 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -6,7 +6,6 @@ import { NextIntlClientProvider } from "next-intl";
import { ThemeProvider } from "@/components/theme-provider";
import { SettingsDialog } from "@/components/settings-dialog";
-
export const metadata: Metadata = {
title: "Judge4c",
description:
@@ -38,4 +37,4 @@ export default async function RootLayout({ children }: RootLayoutProps) {