monaco-editor-lsp-next/src/app/teacher/dashboard/page.tsx

253 lines
8.6 KiB
TypeScript
Raw Normal View History

"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>
);
}