monaco-editor-lsp-next/src/app/teacher/dashboard/page.tsx
2025-06-18 15:51:03 +08:00

253 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getDashboardStats, ProblemCompletionData, DifficultProblemData } from "@/actions/teacher-dashboard";
const ITEMS_PER_PAGE = 5; // 每页显示的题目数量
const chartConfig = {
completed: {
label: "已完成",
color: "#4CAF50", // 使用更鲜明的颜色
},
uncompleted: {
label: "未完成",
color: "#FFA726", // 使用更鲜明的颜色
},
} satisfies ChartConfig;
export default function TeacherDashboard() {
const [currentPage, setCurrentPage] = useState(1);
const [chartData, setChartData] = useState<ProblemCompletionData[]>([]);
const [difficultProblems, setDifficultProblems] = useState<DifficultProblemData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const data = await getDashboardStats();
setChartData(data.problemData);
setDifficultProblems(data.difficultProblems);
} catch (err) {
setError(err instanceof Error ? err.message : '获取数据失败');
console.error('Failed to fetch dashboard data:', err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const totalPages = Math.ceil(chartData.length / ITEMS_PER_PAGE);
// 获取当前页的数据
const currentPageData = chartData.slice(
(currentPage - 1) * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE
);
if (loading) {
return (
<div className="container mx-auto p-6 space-y-6">
<h1 className="text-3xl font-bold mb-6"></h1>
<div className="flex items-center justify-center h-64">
<div className="text-lg">...</div>
</div>
</div>
);
}
if (error) {
return (
<div className="container mx-auto p-6 space-y-6">
<h1 className="text-3xl font-bold mb-6"></h1>
<div className="flex items-center justify-center h-64">
<div className="text-lg text-red-500">: {error}</div>
</div>
</div>
);
}
return (
<div className="container mx-auto p-6 space-y-6">
<h1 className="text-3xl font-bold mb-6"></h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* 题目完成情况模块 */}
<Card className="min-h-[450px]">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{chartData.length === 0 ? (
<div className="flex items-center justify-center h-64">
<div className="text-lg text-muted-foreground"></div>
</div>
) : (
<>
<ChartContainer config={chartConfig} height={400}>
<BarChart
data={currentPageData}
layout="vertical"
margin={{
top: 20,
right: 30,
left: 40,
bottom: 5,
}}
barCategoryGap={20}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
type="number"
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
/>
<YAxis
dataKey="problemDisplayId"
type="category"
tickLine={false}
tickMargin={10}
width={80}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent />}
/>
<Bar
dataKey="completedPercent"
name="已完成"
fill={chartConfig.completed.color}
radius={[4, 4, 0, 0]}
>
<LabelList
dataKey="completed"
position="right"
fill="#000"
formatter={(value: number) => `${value}`}
/>
</Bar>
<Bar
dataKey="uncompletedPercent"
name="未完成"
fill={chartConfig.uncompleted.color}
radius={[4, 4, 0, 0]}
>
<LabelList
dataKey="uncompleted"
position="right"
fill="#000"
formatter={(value: number) => `${value}`}
/>
</Bar>
</BarChart>
</ChartContainer>
{/* 分页控制 */}
{totalPages > 1 && (
<div className="flex justify-center items-center gap-2 mt-4">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
>
</Button>
<span className="text-sm">
{currentPage} {totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
disabled={currentPage === totalPages}
>
</Button>
</div>
)}
</>
)}
</CardContent>
<CardFooter className="flex-col items-start gap-2 text-sm">
<div className="flex gap-2 leading-none font-medium">
<TrendingUp className="h-4 w-4" />
</div>
<div className="text-muted-foreground leading-none">
/
</div>
</CardFooter>
</Card>
{/* 学生易错题模块 */}
<Card className="min-h-[450px]">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span>{difficultProblems.length}</span>
</div>
{difficultProblems.length === 0 ? (
<div className="flex items-center justify-center h-64">
<div className="text-lg text-muted-foreground"></div>
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{difficultProblems.map((problem) => (
<TableRow key={problem.id}>
<TableCell>{problem.problemDisplayId || problem.id.substring(0, 8)}</TableCell>
<TableCell>{problem.problemTitle}</TableCell>
<TableCell>{problem.problemCount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
</CardContent>
</Card>
</div>
</div>
);
}