import { CalendarCheck, CalendarX, Clock, TrendingUp } from "lucide-react" import { getTranslations } from "next-intl/server" import { Card } from "@/shared/components/ui/card" import { cn } from "@/shared/lib/utils" import type { ParentStudentAttendanceSummary } from "@/modules/parent/types" type AggregateStats = { totalStudents: number avgPresentRate: number totalAbsent: number totalLate: number } /** * 聚合多个子女的考勤统计(纯函数,便于测试)。 */ export function aggregateStats( summaries: ParentStudentAttendanceSummary[], ): AggregateStats { if (summaries.length === 0) { return { totalStudents: 0, avgPresentRate: 0, totalAbsent: 0, totalLate: 0 } } const totalStudents = summaries.length const sumRate = summaries.reduce( (sum, s) => sum + (s.stats.total > 0 ? s.stats.presentRate : 0), 0, ) const avgPresentRate = sumRate / totalStudents const totalAbsent = summaries.reduce((sum, s) => sum + s.stats.absent, 0) const totalLate = summaries.reduce((sum, s) => sum + s.stats.late, 0) return { totalStudents, avgPresentRate, totalLate, totalAbsent } } /** * 根据出勤率返回语气色调(纯函数,便于测试)。 */ export function rateTone(rate: number): "good" | "warn" | "bad" { if (rate >= 95) return "good" if (rate >= 90) return "warn" return "bad" } const TONE_STYLES: Record<"good" | "warn" | "bad", string> = { good: "text-emerald-600", warn: "text-amber-600", bad: "text-destructive", } /** * 家长考勤页顶部的出勤率汇总卡片(RSC)。 * 聚合所有子女的出勤率、缺勤、迟到总数,让家长一眼掌握整体情况。 * * P2-8 修复:从 client component 降级为 RSC,使用 `getTranslations`。 */ export async function ParentAttendanceRateCard({ summaries, }: { summaries: ParentStudentAttendanceSummary[] }) { const t = await getTranslations("attendance") const stats = aggregateStats(summaries) if (stats.totalStudents === 0) return null const tone = rateTone(stats.avgPresentRate) const rateLabel = stats.avgPresentRate >= 95 ? t("parent.rateExcellent") : stats.avgPresentRate >= 90 ? t("parent.rateNeedsAttention") : t("parent.rateBelowStandard") return (
{t("stats.attendanceRate")}
{stats.avgPresentRate.toFixed(1)}%
{rateLabel}
{t("parent.children")}
{stats.totalStudents}
{t("parent.linked")}
{t("stats.absent")}
0 && "text-destructive", )} > {stats.totalAbsent}
{t("parent.thisPeriod")}
{t("stats.late")}
0 && "text-amber-600", )} > {stats.totalLate}
{t("parent.thisPeriod")}
) }