Files
NextEdu/src/modules/attendance/components/student-attendance-view.tsx
SpecialX f62b8c0f86 refactor(attendance,elective): 审计第二轮 — 全量完成 P0/P1 改进项
P0 修复:
- 页面层 i18n 全量补齐(admin/teacher/parent/student × attendance/elective)
- types.ts 状态标签常量迁移至 constants.ts(i18n key + Badge variant)
- 修复 getTranslations 导入路径(next-intl → next-intl/server)

P1 改进:
- 解耦 parent 模块对 attendance 类型的直接依赖(本地 view-model 类型)
- 导出纯函数(computeStats/buildWarnings/buildLotteryRankCase 等)
- 统一空状态为 EmptyState 组件
- 清理死代码读 Action(attendance 5 个 + elective 3 个)
- 预留监控埋点接口(trackEvent 13 个新事件名)
- 补齐骨架屏 loading.tsx(8 个页面)
- AlertDialog 替换 window.confirm(student-selection-view)
- a11y 改进(aria-label/role/键盘导航)

修复:
- AttendanceStatus 从 constants.ts 重导出,消除 types/constants 双源混乱
- buildWarnings 的 Translator 类型改用 ReturnType<typeof useTranslations>
2026-06-22 17:33:29 +08:00

112 lines
3.5 KiB
TypeScript

import { useTranslations } from "next-intl"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/shared/components/ui/table"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { CalendarCheck } from "lucide-react"
import { AttendanceStatsCard } from "./attendance-stats-card"
import {
ATTENDANCE_STATUS_BADGE_VARIANTS,
ATTENDANCE_STATUS_LABEL_KEYS,
} from "../constants"
import type { StudentAttendanceSummary } from "../types"
export function StudentAttendanceView({
summary,
}: {
summary: StudentAttendanceSummary | null
}) {
const t = useTranslations("attendance")
if (!summary) {
return (
<EmptyState
title={t("list.empty")}
description={t("errors.notFound")}
icon={CalendarCheck}
className="border-none shadow-none"
/>
)
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("list.columns.student")}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{summary.studentName}</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("stats.totalRecords")}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{summary.stats.total}</p>
</CardContent>
</Card>
</div>
<AttendanceStatsCard stats={summary.stats} />
{summary.recentRecords.length === 0 ? (
<EmptyState
title={t("list.empty")}
description={t("list.emptyDescription")}
icon={CalendarCheck}
className="border-none shadow-none"
/>
) : (
<Card>
<CardHeader>
<CardTitle>{t("stats.recentRecords")}</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("list.columns.date")}</TableHead>
<TableHead>{t("list.columns.class")}</TableHead>
<TableHead>{t("list.columns.status")}</TableHead>
<TableHead>{t("list.columns.remark")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{summary.recentRecords.map((r) => (
<TableRow key={r.id}>
<TableCell className="font-medium">{r.date}</TableCell>
<TableCell>{r.className}</TableCell>
<TableCell>
<Badge variant={ATTENDANCE_STATUS_BADGE_VARIANTS[r.status]} className="capitalize">
{t(ATTENDANCE_STATUS_LABEL_KEYS[r.status])}
</Badge>
</TableCell>
<TableCell className="text-muted-foreground">{r.remark ?? "-"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
)}
</div>
)
}