refactor(dashboard): V2 审计重构 — i18n 补齐 + 共享抽象 + 单测 + a11y

V2 审计报告(docs/architecture/audit/dashboard-audit-report-v2.md)发现并修复:

- P0 i18n:10 个子组件硬编码字符串全部接入 next-intl(teacher-quick-actions /
  teacher-classes-card / teacher-homework-card / teacher-schedule /
  recent-submissions / teacher-grade-trends / student-grades-card /
  student-today-schedule-card / student-upcoming-assignments-card /
  admin-dashboard),新增 ~50 个翻译键
- P1 共享抽象:新增 DashboardGreetingHeader 组件,消除 teacher/student
  头部 90% 重复代码,两个 Header 改为薄包装
- P2 单测:为 6 个纯函数添加 31 个单元测试
  (tests/integration/dashboard/dashboard-utils.test.ts)
- P2 a11y:admin 表格 caption、teacher/student 视图语义化标签
  (header / section aria-label / aside aria-label)
- 同步架构图 004/005
This commit is contained in:
SpecialX
2026-06-22 17:01:00 +08:00
parent 10c668f36a
commit e997abaf5e
41 changed files with 1811 additions and 516 deletions

View File

@@ -2,7 +2,9 @@
import Link from "next/link"
import { BarChart3 } from "lucide-react"
import { useTranslations } from "next-intl"
import { Button } from "@/shared/components/ui/button"
import { ChartCardShell } from "@/shared/components/charts/chart-card-shell"
import { TrendLineChart } from "@/shared/components/charts/trend-line-chart"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/shared/components/ui/table"
@@ -10,6 +12,7 @@ import { formatDate } from "@/shared/lib/utils"
import type { StudentDashboardGradeProps } from "@/modules/homework/types"
export function StudentGradesCard({ grades }: { grades: StudentDashboardGradeProps }) {
const t = useTranslations("dashboard")
const hasGradeTrend = grades.trend.length > 0
const hasRecentGrades = grades.recent.length > 0
@@ -26,13 +29,20 @@ export function StudentGradesCard({ grades }: { grades: StudentDashboardGradePro
return (
<ChartCardShell
title="Recent Grades"
title={t("sections.recentGrades")}
icon={BarChart3}
iconClassName="text-muted-foreground"
isEmpty={!hasGradeTrend}
emptyTitle="No graded work yet"
emptyDescription="Finish and submit assignments to see your score trend."
emptyTitle={t("empty.noGradedWork")}
emptyDescription={t("empty.noGradedWorkDesc")}
emptyClassName="h-72"
action={
hasGradeTrend ? (
<Button asChild variant="outline" size="sm">
<Link href="/student/grades">{t("quickActions.viewAll")}</Link>
</Button>
) : null
}
>
<div className="space-y-4">
<div className="rounded-md border bg-card p-4">
@@ -41,7 +51,7 @@ export function StudentGradesCard({ grades }: { grades: StudentDashboardGradePro
series={[
{
dataKey: "score",
name: "Score (%)",
name: t("chart.scorePercent"),
color: "hsl(var(--primary))",
dotRadius: 4,
activeDotRadius: 6,
@@ -56,13 +66,13 @@ export function StudentGradesCard({ grades }: { grades: StudentDashboardGradePro
{latestGrade ? (
<div className="mt-3 flex items-center justify-between text-sm text-muted-foreground">
<div>
Latest:{" "}
{t("chart.latest")}:{" "}
<span className="font-medium text-foreground tabular-nums">
{Math.round(latestGrade.percentage)}%
</span>
</div>
<div>
Points:{" "}
{t("chart.points")}:{" "}
<span className="font-medium text-foreground tabular-nums">
{latestGrade.score}/{latestGrade.maxScore}
</span>
@@ -76,9 +86,9 @@ export function StudentGradesCard({ grades }: { grades: StudentDashboardGradePro
<Table>
<TableHeader>
<TableRow className="bg-muted/50">
<TableHead className="text-xs font-medium uppercase text-muted-foreground">Assignment</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">Score</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">When</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.assignment")}</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.score")}</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.when")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>