Files
NextEdu/src/modules/parent/components/parent-student-attendance-detail.tsx
SpecialX e9a5264fe7 feat(parent,auth,onboarding,files,notifications,adaptive-practice,ai): add module updates
parent:

- Add parent-student-attendance-detail component

auth:

- Add actions, data-access, schema, services, types

onboarding:

- Add parent-children-form and hooks directory

files:

- Add actions, schema, hooks directory

notifications:

- Add schema and schema test

adaptive-practice:

- Add answer-input, answer-result, practice-result-view, practice-starter-with-nav

- Add question-card, question-content, lib and services directories

ai:

- Add context/create-ai-client-service, hooks/use-drag-position, hooks/use-position-persistence
2026-07-03 10:26:12 +08:00

164 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useTranslations } from "next-intl"
import {
CalendarCheck,
CheckCircle2,
Clock,
TrendingUp,
Users,
XCircle,
Inbox,
} from "lucide-react"
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 { StatItem } from "@/shared/components/ui/stat-item"
import {
ATTENDANCE_STATUS_BADGE_VARIANTS,
ATTENDANCE_STATUS_LABEL_KEYS,
} from "@/shared/constants/attendance-status"
import type { ParentStudentAttendanceSummary } from "@/modules/parent/types"
/**
* 家长视角的单个子女考勤详情组件(统计卡片 + 最近记录表)。
*
* 解耦说明P1-2 修复):
* - 此组件替代原先从 `@/modules/attendance/components/student-attendance-view` 直接导入的 `StudentAttendanceView`。
* - parent 模块不再依赖 attendance 模块的 UI 组件,仅依赖自身类型与 shared 常量。
* - 数据通过 props 注入(由 parent page 通过 `AttendanceReadService` 接口获取后传入)。
*/
export function ParentStudentAttendanceDetail({
summary,
}: {
summary: ParentStudentAttendanceSummary
}) {
const t = useTranslations("attendance")
const { stats, recentRecords } = summary
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">{stats.total}</p>
</CardContent>
</Card>
</div>
{stats.total === 0 ? (
<EmptyState
title={t("stats.noData")}
description={t("stats.noDataDescription")}
icon={CalendarCheck}
className="border-none shadow-none"
/>
) : (
<Card>
<CardHeader>
<CardTitle>{t("title.teacherStats")}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
<StatItem
label={t("stats.totalRecords")}
value={stats.total}
icon={<Users className="h-4 w-4" />}
/>
<StatItem
label={t("stats.present")}
value={stats.present}
icon={<CheckCircle2 className="h-4 w-4" />}
/>
<StatItem
label={t("stats.absent")}
value={stats.absent}
icon={<XCircle className="h-4 w-4" />}
/>
<StatItem
label={t("stats.late")}
value={stats.late}
icon={<Clock className="h-4 w-4" />}
/>
<StatItem
label={t("stats.attendanceRate")}
value={`${stats.presentRate.toFixed(1)}%`}
icon={<TrendingUp className="h-4 w-4" />}
/>
</div>
</CardContent>
</Card>
)}
{recentRecords.length === 0 ? (
<EmptyState
title={t("list.empty")}
description={t("list.emptyDescription")}
icon={Inbox}
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.status")}</TableHead>
<TableHead>{t("list.columns.remark")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{recentRecords.map((r) => (
<TableRow key={r.id}>
<TableCell className="font-medium">{r.date}</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>
)
}