Files
NextEdu/src/modules/grades/components/class-comparison-chart.tsx
SpecialX 3b6272c99d feat: 完成 P1 全部功能 + 修复 proxy 导出 + 切换 MySQL 端口至 14013
## P1 功能(20 项)
- 站内消息系统、家长仪表盘、学生考勤管理
- Excel 导入导出、用户批量导入、成绩导出
- 排课规则+自动排课+课表调整
- 成绩趋势+对比分析、密码安全策略、速率限制
- 数据变更日志、文件预览+存储策略、全文检索
- 依赖审计集成 CI、数据库定时备份、E2E 测试完善
- 通知偏好管理

## 基础设施修复
- src/proxy.ts: 将 middleware 导出重命名为 proxy(Next.js 16 要求)
- .env: MySQL 端口从 13002 切换至 14013
- scripts/create-db.ts: 新增数据库初始化脚本

## 架构文档同步
- 004_architecture_impact_map.md 和 005_architecture_data.json
  完整记录所有新增表、模块、路由、权限、依赖关系
2026-06-17 13:44:37 +08:00

133 lines
3.6 KiB
TypeScript

"use client"
import { BarChart3 } from "lucide-react"
import {
Bar,
BarChart,
CartesianGrid,
Legend,
XAxis,
YAxis,
} from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/shared/components/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/shared/components/ui/chart"
import { EmptyState } from "@/shared/components/ui/empty-state"
import type { ClassComparisonItem } from "@/modules/grades/types"
const chartConfig = {
averageScore: { label: "Average (%)", color: "hsl(var(--primary))" },
passRate: { label: "Pass Rate (%)", color: "hsl(var(--chart-2))" },
excellentRate: { label: "Excellent (%)", color: "hsl(var(--chart-3))" },
}
interface ClassComparisonChartProps {
data: ClassComparisonItem[]
}
export function ClassComparisonChart({ data }: ClassComparisonChartProps) {
if (!data || data.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-4 w-4" />
Class Comparison
</CardTitle>
<CardDescription>
Compare average, pass rate, and excellent rate across classes.
</CardDescription>
</CardHeader>
<CardContent>
<EmptyState
icon={BarChart3}
title="No comparison data"
description="Select a grade and subject to compare classes."
className="border-none h-60"
/>
</CardContent>
</Card>
)
}
const chartData = data.map((d) => ({
name: d.className,
averageScore: d.averageScore,
passRate: d.passRate,
excellentRate: d.excellentRate,
count: d.count,
studentCount: d.studentCount,
}))
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-4 w-4" />
Class Comparison
</CardTitle>
<CardDescription>
Average score, pass rate (60%), and excellent rate (85%) per class.
</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="h-[300px] w-full">
<BarChart
data={chartData}
margin={{ left: 8, right: 8, top: 8, bottom: 8 }}
>
<CartesianGrid
vertical={false}
strokeDasharray="4 4"
strokeOpacity={0.4}
/>
<XAxis
dataKey="name"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value: string) =>
value.length > 8 ? `${value.slice(0, 8)}...` : value
}
/>
<YAxis
domain={[0, 100]}
tickLine={false}
axisLine={false}
tickFormatter={(value: number) => `${value}%`}
width={36}
/>
<ChartTooltip content={<ChartTooltipContent className="w-[240px]" />} />
<Legend />
<Bar
dataKey="averageScore"
fill="var(--color-averageScore)"
radius={[4, 4, 0, 0]}
/>
<Bar
dataKey="passRate"
fill="var(--color-passRate)"
radius={[4, 4, 0, 0]}
/>
<Bar
dataKey="excellentRate"
fill="var(--color-excellentRate)"
radius={[4, 4, 0, 0]}
/>
</BarChart>
</ChartContainer>
</CardContent>
</Card>
)
}