Files
NextEdu/src/modules/parent/components/parent-attendance-warning.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

119 lines
3.4 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 { AlertTriangle, Phone } 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 Warning = {
studentId: string
studentName: string
message: string
severity: "high" | "medium"
}
/** 翻译函数类型(与 useTranslations / getTranslations 返回值兼容) */
type Translator = (
key: string,
values?: Record<string, string | number | Date>,
) => string
/**
* 构建考勤异常预警列表(纯函数,便于测试)。
*/
export function buildWarnings(
summaries: ParentStudentAttendanceSummary[],
t: Translator,
): Warning[] {
const warnings: Warning[] = []
for (const s of summaries) {
const { stats, studentName, studentId } = s
if (stats.absent >= 3) {
warnings.push({
studentId,
studentName,
message: t("parent.absentHighSeverity", { count: stats.absent }),
severity: "high",
})
} else if (stats.absent >= 1) {
warnings.push({
studentId,
studentName,
message: t("parent.absentWarning", { count: stats.absent }),
severity: "medium",
})
}
if (stats.late >= 3) {
warnings.push({
studentId,
studentName,
message: t("parent.lateWarning", { count: stats.late }),
severity: "medium",
})
}
if (stats.presentRate < 90 && stats.total > 0) {
warnings.push({
studentId,
studentName,
message: t("parent.lowRateWarning", { rate: stats.presentRate.toFixed(1) }),
severity: "high",
})
}
}
return warnings
}
/**
* 家长视角的考勤异常预警横幅RSC
* 聚合所有子女的考勤异常(缺勤、迟到、低出勤率),提醒家长及时关注。
*
* P2-8 修复:从 client component 降级为 RSC使用 `getTranslations`。
*/
export async function ParentAttendanceWarning({
summaries,
}: {
summaries: ParentStudentAttendanceSummary[]
}) {
const t = await getTranslations("attendance")
const warnings = buildWarnings(summaries, t)
if (warnings.length === 0) return null
return (
<Card
className={cn(
"border-destructive/30 bg-destructive/5 p-4",
warnings.some((w) => w.severity === "high") && "border-destructive/50",
)}
aria-label={t("parent.warningTitle")}
>
<div className="flex items-start gap-3">
<AlertTriangle
className="h-5 w-5 text-destructive shrink-0 mt-0.5"
aria-hidden
/>
<div className="flex-1 space-y-2">
<div className="text-sm font-semibold text-destructive">
{t("parent.warningTitle")}
</div>
<ul className="space-y-1.5 text-sm">
{warnings.map((w, idx) => (
<li key={`${w.studentId}-${idx}`} className="flex items-start gap-2">
<span className="font-medium shrink-0">{w.studentName}:</span>
<span className="text-muted-foreground">{w.message}</span>
</li>
))}
</ul>
<div className="flex items-center gap-2 pt-1 text-xs text-muted-foreground">
<Phone className="h-3 w-3" aria-hidden />
<span>{t("parent.contactHomeroom")}</span>
</div>
</div>
</div>
</Card>
)
}