"use client"; /** * 考勤报表页 - 客户端组件(ARCHITECTURE.md §7.3 详情页 / §9.1 / §10 P2) * * 数据契约: * - 报表查询 attendanceReport(classId, range):❌ schema 无此字段 → MSW 兜底(@contract-pending) * - 契约工单:docs/architecture/issues/contracts/classes_contract.md#attendance-report * * 三态规范(§11.3 DoD): * - loading:DetailPageSkeleton * - error:errorNode 局部降级 * - empty:data 为 null 时显示空态节点 * * 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4 */ import { FileText } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useAttendanceReport, type AttendanceReportItem } from "@/lib/api"; import { EmptyState } from "@/shared/components/ui/empty-state"; import { DetailPageShell, DetailPageSkeleton, DetailSection, DetailField, } from "@/shared/components/page-templates"; import { attendanceRateToColorClass, formatAttendanceRate, } from "@/features/teacher/attendance/transformations"; /** * 报表客户端主体。需由 server page 包裹在 中。 */ export function AttendanceReportClient(): React.ReactElement { const t = useTranslations("attendance"); const tCommon = useTranslations("common"); const searchParams = useSearchParams(); const classId = searchParams.get("classId") ?? "cls-001"; const startDate = searchParams.get("startDate") ?? undefined; const endDate = searchParams.get("endDate") ?? undefined; // @contract-pending:MSW 兜底 const { data, loading, error } = useAttendanceReport( classId, startDate, endDate, ); const errorNode = error ? (

{tCommon("error.loadFailed", { message: String(error) })}

{t("report.mswNotice")}

) : undefined; const emptyNode = !loading && !error && !data ? ( ) : undefined; return ( } backHref="/shell/teacher/attendance" loading={loading} loadingNode={} errorNode={errorNode} emptyNode={emptyNode} > {data ? : null} {data ? : null} ); } /** * 报表汇总区。 */ function ReportSummarySection({ data, }: { data: NonNullable["data"]>; }): React.ReactElement { const t = useTranslations("attendance"); const summary = data.summary; return ( {formatAttendanceRate(summary.attendanceRate)} } /> ); } /** * 报表明细区(按学生聚合)。 */ function ReportItemsSection({ items, }: { items: AttendanceReportItem[]; }): React.ReactElement { const t = useTranslations("attendance"); return (
{items.map((item) => ( ))}
{t("report.colStudentName")} {t("report.colPresent")} {t("report.colAbsent")} {t("report.colLate")} {t("report.colLeave")} {t("report.colAttendanceRate")}
{item.studentName} {item.present} {item.absent} {item.late} {item.leave} {formatAttendanceRate(item.attendanceRate)}
); }