import type { JSX } from "react"; import { useMemo } from "react"; import { useTranslations } from "next-intl"; import { cn } from "@/shared/lib/utils"; import type { GradeOption, SubjectOption } from "@/modules/school/data-access"; import type { StandardsCoverageCell } from "@/modules/lesson-preparation/data-access-analytics"; interface Props { grades: GradeOption[]; subjects: SubjectOption[]; heatmap: StandardsCoverageCell[]; } /** 根据覆盖率返回背景色 */ function getCoverageColor(percent: number): string { if (percent === 0) return "bg-muted/40 text-muted-foreground"; if (percent < 25) return "bg-red-100 text-red-700 dark:bg-red-950/50 dark:text-red-300"; if (percent < 50) return "bg-amber-100 text-amber-700 dark:bg-amber-950/50 dark:text-amber-300"; if (percent < 75) return "bg-blue-100 text-blue-700 dark:bg-blue-950/50 dark:text-blue-300"; return "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-300"; } export function CurriculumMapView({ grades, subjects, heatmap }: Props): JSX.Element { const t = useTranslations("lessonPreparation"); // 构建查找表:subjectId|gradeId -> cell const cellMap = useMemo(() => { const map = new Map(); for (const cell of heatmap) { const key = `${cell.subjectId ?? ""}|${cell.gradeId ?? ""}`; map.set(key, cell); } return map; }, [heatmap]); if (grades.length === 0 || subjects.length === 0) { return (
暂无年级或学科数据
); } return (
{/* arbitrary-value: table column min width */} {grades.map((g) => ( ))} {subjects.map((s) => ( {/* arbitrary-value: table column min width */} {grades.map((g) => { const key = `${s.id}|${g.id}`; const cell = cellMap.get(key); const total = cell?.totalPlans ?? 0; const linked = cell?.standardsLinkedPlans ?? 0; const percent = cell?.coveragePercent ?? 0; return ( ); })} ))}
学科\年级 {g.name}
{s.name} {total === 0 ? ( ) : (
{percent}% {linked}/{total}
)}
{/* 图例 */}
{t("v4.heatmap.legendTitle")}
); } function LegendItem({ color, label }: { color: string; label: string }): JSX.Element { return ( {label} ); }