"use client" import { PieChart as PieChartIcon } from "lucide-react" import { useTranslations } from "next-intl" import { ChartCardShell } from "@/shared/components/charts/chart-card-shell" import { SimpleBarChart } from "@/shared/components/charts/simple-bar-chart" import type { GradeDistributionResult } from "@/modules/grades/types" /** * v4-P3-4: 色盲友好的双重编码。 * 每个分数段使用不同的 SVG pattern(条纹/点状/交叉线等)+ 颜色, * 确保色觉障碍用户能通过纹理区分各分数段。 */ const BUCKET_FILLS: Record = { "90-100": "url(#grade-pattern-90-100)", "80-89": "url(#grade-pattern-80-89)", "70-79": "url(#grade-pattern-70-79)", "60-69": "url(#grade-pattern-60-69)", "<60": "url(#grade-pattern-lt60)", } /** v4-P3-4: SVG 图案定义,为每个分数段提供独特的纹理 */ const PATTERN_DEFS = ( {/* 90-100: 正向斜条纹 */} {/* 80-89: 圆点 */} {/* 70-79: 交叉线 */} {/* 60-69: 反向斜条纹 */} {/* <60: 网格 */} ) interface GradeDistributionChartProps { data: GradeDistributionResult | null } interface DistributionTooltipItem { label: string count: number percentage: number } interface DistributionTooltipPayload { payload?: DistributionTooltipItem } function isDistributionTooltipPayload(v: unknown): v is DistributionTooltipPayload { if (typeof v !== "object" || v === null) return false const obj = v as Record const inner = obj.payload if (inner === undefined || inner === null) return true if (typeof inner !== "object") return false const item = inner as Record return ( typeof item.label === "string" && typeof item.count === "number" && typeof item.percentage === "number" ) } export function GradeDistributionChart({ data }: GradeDistributionChartProps) { const t = useTranslations("grades") const isEmpty = !data || data.totalCount === 0 const chartData = isEmpty ? [] : data.buckets.map((b) => ({ label: b.label, count: b.count, percentage: data.totalCount > 0 ? Math.round((b.count / data.totalCount) * 1000) / 10 : 0, })) return (
{ if (!isDistributionTooltipPayload(payload)) return null const item = payload.payload if (!item) return null return (
{item.label}: {t("distribution.tooltipStudents", { count: item.count })} {t("distribution.tooltipOfTotal", { percentage: item.percentage })}
) }} />
) }