Files
NextEdu/src/modules/dashboard/components/teacher-dashboard/teacher-grade-trends.tsx
SpecialX 1a9377222c feat(app): add error/loading boundaries and update dashboard routes
- Add error.tsx and loading.tsx boundaries for admin, parent, student, teacher routes

- Add dashboard-error-fallback and dashboard-loading-skeleton components

- Add student/learning page, parent/leave routes, teacher textbook components

- Update existing app routes across auth, dashboard, and API endpoints

- Update proxy middleware and next-auth type declarations
2026-06-23 17:38:28 +08:00

79 lines
2.7 KiB
TypeScript

"use client"
import { TrendingUp } from "lucide-react"
import { useTranslations } from "next-intl"
import { ChartCardShell } from "@/shared/components/charts/chart-card-shell"
import { TrendLineChart } from "@/shared/components/charts/trend-line-chart"
import type { TeacherGradeTrendItem } from "@/modules/homework/types"
export function TeacherGradeTrends({ trends }: { trends: TeacherGradeTrendItem[] }) {
const t = useTranslations("dashboard")
const hasTrends = trends.length > 0
const chartData = trends.map((item) => {
const percentage = item.maxScore > 0 ? (item.averageScore / item.maxScore) * 100 : 0
return {
title: item.title,
score: Math.round(percentage),
fullTitle: item.title,
submissionCount: item.submissionCount,
totalStudents: item.totalStudents,
}
})
return (
<ChartCardShell
title={t("sections.classPerformance")}
description={t("chart.classPerformanceDesc", { count: trends.length })}
icon={TrendingUp}
iconClassName="text-primary"
titleClassName="text-base font-medium"
isEmpty={!hasTrends}
emptyTitle={t("empty.noData")}
emptyDescription={t("empty.noDataDesc")}
emptyClassName="h-[200px] p-0"
className="col-span-1"
>
<div className="space-y-4">
<TrendLineChart
data={chartData}
series={[
{
dataKey: "score",
name: t("chart.averageScorePercent"),
color: "hsl(var(--primary))",
dotRadius: 4,
activeDotRadius: 6,
},
]}
heightClassName="h-[200px]"
margin={{ left: 12, right: 12, top: 12, bottom: 12 }}
yWidth={30}
tooltipClassName="w-[200px]"
/>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{chartData
.slice()
.reverse()
.slice(0, 3)
.map((item, i) => (
<div key={item.fullTitle || `item-${i}`} className="flex flex-col gap-1 rounded-lg border p-3 bg-card/50">
<div className="text-xs text-muted-foreground truncate" title={item.fullTitle}>
{item.fullTitle}
</div>
<div className="flex items-baseline gap-2">
<span className="text-xl font-bold tabular-nums">{item.score}%</span>
</div>
<div className="text-[10px] text-muted-foreground">
{t("chart.submittedCount", { submitted: item.submissionCount, total: item.totalStudents })}
</div>
</div>
))}
</div>
</div>
</ChartCardShell>
)
}