feat(error-book): add analytics stats, charts, and error collection
- Add analytics-stats-cards, chapter-weakness-chart, class-error-bar-chart - Add knowledge-point-weakness-chart, subject-distribution-chart, subject-tabs - Add class-filter and grouped-student-error-table components - Add data-access-collection for error aggregation from multiple sources - Update error-book-detail-dialog, data-access, and types
This commit is contained in:
97
src/modules/error-book/components/analytics-stats-cards.tsx
Normal file
97
src/modules/error-book/components/analytics-stats-cards.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { BookOpen, Brain, CheckCircle2, Clock, TrendingUp } from "lucide-react"
|
||||
import { Card, CardContent } from "@/shared/components/ui/card"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
|
||||
interface AnalyticsStatsCardsProps {
|
||||
totalStudents: number
|
||||
studentsWithErrorBook: number
|
||||
totalErrorItems: number
|
||||
averageMasteryRate: number
|
||||
dueReviewCount: number
|
||||
/** 涉及的知识点数 */
|
||||
knowledgePointCount?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 错题分析统计卡片(教师/管理员视图)
|
||||
* 5 个卡片:覆盖学生/错题总数/平均掌握率/待复习/知识点数
|
||||
*/
|
||||
export function AnalyticsStatsCards({
|
||||
totalStudents,
|
||||
studentsWithErrorBook,
|
||||
totalErrorItems,
|
||||
averageMasteryRate,
|
||||
dueReviewCount,
|
||||
knowledgePointCount,
|
||||
className,
|
||||
}: AnalyticsStatsCardsProps) {
|
||||
const cards = [
|
||||
{
|
||||
label: "覆盖学生",
|
||||
value: studentsWithErrorBook,
|
||||
sub: `/ ${totalStudents} 人`,
|
||||
icon: BookOpen,
|
||||
color: "text-blue-600 dark:text-blue-400",
|
||||
bg: "bg-blue-50 dark:bg-blue-950/30",
|
||||
},
|
||||
{
|
||||
label: "错题总数",
|
||||
value: totalErrorItems,
|
||||
sub: `人均 ${(totalStudents > 0 ? totalErrorItems / totalStudents : 0).toFixed(1)} 题`,
|
||||
icon: TrendingUp,
|
||||
color: "text-rose-600 dark:text-rose-400",
|
||||
bg: "bg-rose-50 dark:bg-rose-950/30",
|
||||
},
|
||||
{
|
||||
label: "平均掌握率",
|
||||
value: `${Math.round(averageMasteryRate * 100)}%`,
|
||||
sub: averageMasteryRate >= 0.6 ? "整体良好" : "需加强",
|
||||
icon: CheckCircle2,
|
||||
color: "text-emerald-600 dark:text-emerald-400",
|
||||
bg: "bg-emerald-50 dark:bg-emerald-950/30",
|
||||
},
|
||||
{
|
||||
label: "待复习",
|
||||
value: dueReviewCount,
|
||||
sub: dueReviewCount > 0 ? "需要关注" : "无到期",
|
||||
icon: Clock,
|
||||
color: "text-amber-600 dark:text-amber-400",
|
||||
bg: "bg-amber-50 dark:bg-amber-950/30",
|
||||
},
|
||||
{
|
||||
label: "涉及知识点",
|
||||
value: knowledgePointCount ?? 0,
|
||||
sub: knowledgePointCount && knowledgePointCount > 5 ? "范围较广" : "集中",
|
||||
icon: Brain,
|
||||
color: "text-purple-600 dark:text-purple-400",
|
||||
bg: "bg-purple-50 dark:bg-purple-950/30",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-5", className)}>
|
||||
{cards.map((card) => {
|
||||
const Icon = card.icon
|
||||
return (
|
||||
<Card key={card.label} className="overflow-hidden">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-xs text-muted-foreground">{card.label}</div>
|
||||
<div className={cn("mt-1 text-2xl font-bold", card.color)}>
|
||||
{card.value}
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs text-muted-foreground">{card.sub}</div>
|
||||
</div>
|
||||
<div className={cn("rounded-lg p-2", card.bg)}>
|
||||
<Icon className={cn("h-4 w-4", card.color)} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user