- 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
79 lines
2.7 KiB
TypeScript
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>
|
|
)
|
|
}
|