refactor(grades,diagnostic): 完成成绩和学情诊断模块审计 P1+P2 改进项

P1-1: 抽取 stats-service.ts,将 8 个统计计算纯函数从 data-access 层分离
P1-5: 创建 WidgetBoundary 组件 + 补齐 teacher 路由 loading.tsx/error.tsx (14 文件)
P1-6: 同步架构图文档 004/005,新增 stats-service 与 widget-boundary 节点
P2-1: 补充 a11y ARIA 属性(5 图表 role=img + aria-label,2 表格 caption,3 列表 role=list,3 按钮 aria-label)
P2-3: 修复班级报告 studentId 字段语义错误(schema 改为可空 + 迁移 + 代码适配)
P2-4: 修复 grade_managed scope 返回空数据(改为子查询 classes 表按 gradeId 过滤)
P2-5: 新增 /parent/diagnostic/ 页面(多子女学情诊断聚合 + loading + error)
P2-6: 统一 SearchParams 工具(student/grades 和 management/grade/insights 改用 @/shared/lib/search-params)
This commit is contained in:
SpecialX
2026-06-22 17:07:32 +08:00
parent e997abaf5e
commit 5f3a1a4662
41 changed files with 9043 additions and 381 deletions

View File

@@ -70,37 +70,39 @@ export function GradeDistributionChart({ data }: GradeDistributionChartProps) {
emptyDescription="Select a class and subject to view score distribution."
emptyClassName="h-60"
>
<SimpleBarChart
data={chartData}
bars={[
{
dataKey: "count",
name: "Students",
color: "hsl(var(--primary))",
},
]}
xKey="label"
xTickFormatter={null}
yAllowDecimals={false}
yWidth={32}
heightClassName="h-[280px]"
margin={{ left: 8, right: 8, top: 8, bottom: 8 }}
tooltipClassName="w-[200px]"
cellColors={BUCKET_COLORS}
tooltipFormatter={(payload: unknown) => {
if (!isDistributionTooltipPayload(payload)) return null
const item = payload.payload
if (!item) return null
return (
<div className="flex w-full flex-col gap-0.5">
<span className="text-sm font-medium">
{item.label}: {item.count} student{item.count === 1 ? "" : "s"}
</span>
<span className="text-xs text-muted-foreground">{item.percentage}% of total</span>
</div>
)
}}
/>
<div role="img" aria-label={`分数分布柱状图:${isEmpty ? "暂无数据" : `${data.totalCount} 条成绩记录分布在 5 个分数区间`}`}>
<SimpleBarChart
data={chartData}
bars={[
{
dataKey: "count",
name: "Students",
color: "hsl(var(--primary))",
},
]}
xKey="label"
xTickFormatter={null}
yAllowDecimals={false}
yWidth={32}
heightClassName="h-[280px]"
margin={{ left: 8, right: 8, top: 8, bottom: 8 }}
tooltipClassName="w-[200px]"
cellColors={BUCKET_COLORS}
tooltipFormatter={(payload: unknown) => {
if (!isDistributionTooltipPayload(payload)) return null
const item = payload.payload
if (!item) return null
return (
<div className="flex w-full flex-col gap-0.5">
<span className="text-sm font-medium">
{item.label}: {item.count} student{item.count === 1 ? "" : "s"}
</span>
<span className="text-xs text-muted-foreground">{item.percentage}% of total</span>
</div>
)
}}
/>
</div>
</ChartCardShell>
)
}