fix(dashboard): v3 审计修复 — 数据完整性、i18n、类型安全、死代码清理
P0 修复(严重):
- admin ContentRow 标签与值错配(stats.users→textbooks 等 6 处)
- admin/error.tsx 硬编码中文替换为 useTranslations
- UserGrowthChart 空数据时渲染 EmptyState(userGrowth/homeworkTrend 永远为空数组)
P1 修复(高):
- 新增 admin/dashboard 和 student/dashboard 的 loading.tsx + error.tsx
- 抽取 DashboardLoadingSkeleton 和 DashboardErrorFallback 共享组件,消除 5 套重复文件
- formatDate/formatLongDate 传入用户 locale(admin/teacher/student 共 6 个组件)
- 移除死代码:getCachedAdminDashboard、AvatarImage src={undefined}、TeacherStats isLoading prop
- filterTodaySchedule 改为泛型函数,消除 as 类型断言
- 辅助函数 getStatus/getDueUrgency 新增显式返回类型
- UserGrowthChart 新增 labelKey prop 区分用户增长/作业提交趋势标签
P2 修复(中):
- 4 个组件从客户端转为服务端组件(DashboardGreetingHeader、TeacherQuickActions、TeacherDashboardHeader、StudentDashboardHeader)
- Student dashboard 空状态新增 CTA(viewSchedule、viewAll)
- TeacherHomeworkCard 图标按钮新增 aria-label
- TeacherTodoCard 排序逻辑重写为可读的 if/return 模式
同步更新:
- docs/architecture/005_architecture_data.json 新增 DashboardLoadingSkeleton、DashboardErrorFallback 条目
- 新增 docs/architecture/audit/dashboard-audit-report-v3.md 审计报告
- dashboard.json 新增 6 个 i18n 键(textbooks/chapters/questions/exams/totalAssignments/totalSubmissions)
This commit is contained in:
@@ -4,37 +4,14 @@ import { PenTool, TriangleAlert } from "lucide-react"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
import { StatusBadge } from "@/shared/components/ui/status-badge"
|
||||
import { cn, formatDate } from "@/shared/lib/utils"
|
||||
import type { StudentHomeworkProgressStatus } from "@/modules/homework/types"
|
||||
import {
|
||||
STUDENT_HOMEWORK_PROGRESS_VARIANT,
|
||||
STUDENT_HOMEWORK_PROGRESS_LABEL,
|
||||
} from "@/modules/homework/types"
|
||||
import type { ChildHomeworkSummaryData } from "@/modules/parent/types"
|
||||
|
||||
const getStatusVariant = (
|
||||
status: StudentHomeworkProgressStatus,
|
||||
): "default" | "secondary" | "outline" => {
|
||||
switch (status) {
|
||||
case "graded":
|
||||
return "default"
|
||||
case "submitted":
|
||||
case "in_progress":
|
||||
return "secondary"
|
||||
case "not_started":
|
||||
return "outline"
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusLabel = (status: StudentHomeworkProgressStatus): string => {
|
||||
switch (status) {
|
||||
case "graded":
|
||||
return "Graded"
|
||||
case "submitted":
|
||||
return "Submitted"
|
||||
case "in_progress":
|
||||
return "In progress"
|
||||
case "not_started":
|
||||
return "Not started"
|
||||
}
|
||||
}
|
||||
|
||||
type DueUrgency = "overdue" | "urgent" | "normal"
|
||||
|
||||
const getDueUrgency = (dueAt: string | null, now: Date): DueUrgency | null => {
|
||||
@@ -63,7 +40,7 @@ export function ChildHomeworkSummary({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<PenTool className="h-4 w-4 text-muted-foreground" />
|
||||
<PenTool className="h-4 w-4 text-muted-foreground" aria-hidden />
|
||||
{childName}'s Homework
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -83,7 +60,7 @@ export function ChildHomeworkSummary({
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/50 p-2">
|
||||
<div className="text-xs text-muted-foreground flex items-center justify-center gap-1">
|
||||
<TriangleAlert className="h-3 w-3" />
|
||||
<TriangleAlert className="h-3 w-3" aria-hidden />
|
||||
Overdue
|
||||
</div>
|
||||
<div
|
||||
@@ -112,18 +89,29 @@ export function ChildHomeworkSummary({
|
||||
{summary.recentAssignments.map((a) => {
|
||||
const urgency = getDueUrgency(a.dueAt, now)
|
||||
const isGraded = a.progressStatus === "graded"
|
||||
const scoreText = a.latestScore !== null ? `${a.latestScore} pts` : isGraded ? "Graded" : "-"
|
||||
return (
|
||||
<Link
|
||||
key={a.id}
|
||||
href={`/parent/children/${childId}?tab=homework`}
|
||||
className="flex items-center justify-between rounded-md border bg-card p-3 hover:bg-muted/50 transition-colors"
|
||||
className="flex min-h-[44px] items-center justify-between rounded-md border bg-card p-3 hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<div className="font-medium text-sm truncate">{a.title}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{a.subjectName ? (
|
||||
<Badge variant="outline" className="text-[10px] shrink-0">
|
||||
{a.subjectName}
|
||||
</Badge>
|
||||
) : null}
|
||||
<div className="font-medium text-sm truncate">{a.title}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Badge variant={getStatusVariant(a.progressStatus)} className="text-[10px]">
|
||||
{getStatusLabel(a.progressStatus)}
|
||||
</Badge>
|
||||
<StatusBadge
|
||||
status={a.progressStatus}
|
||||
variantMap={STUDENT_HOMEWORK_PROGRESS_VARIANT}
|
||||
labelMap={STUDENT_HOMEWORK_PROGRESS_LABEL}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
{a.dueAt ? (
|
||||
<span
|
||||
className={cn(
|
||||
@@ -136,14 +124,14 @@ export function ChildHomeworkSummary({
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-medium tabular-nums shrink-0 ml-2">
|
||||
{a.latestScore ?? "-"}
|
||||
{scoreText}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
<Link
|
||||
href={`/parent/children/${childId}?tab=homework`}
|
||||
className="block text-center text-xs text-muted-foreground hover:text-foreground transition-colors pt-1"
|
||||
className="block text-center text-xs text-muted-foreground hover:text-foreground transition-colors pt-1 min-h-[36px] flex items-center justify-center"
|
||||
>
|
||||
View all
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user