// AttendanceWarningBanner:考勤异常预警横幅 // 依据:参考项目差距补齐 - 考勤异常预警 // - 缺勤≥3 高危(红色) // - 迟到≥3 中危(黄色) // - 出勤率<90% 高危(红色) // - 可关闭(本地 state,不持久化) "use client"; import { useState, useMemo } from "react"; import type { AttendanceRecord } from "@/types"; import { cn, formatPercent } from "@/lib/utils"; interface AttendanceWarningBannerProps { records: AttendanceRecord[]; } interface AttendanceStats { present: number; late: number; absent: number; leave: number; total: number; rate: number; } function calculateStats(records: AttendanceRecord[]): AttendanceStats { const counts = { present: 0, late: 0, absent: 0, leave: 0 }; for (const r of records) { counts[r.status]++; } const total = records.length; const attended = counts.present + counts.late; const rate = total > 0 ? attended / total : 1; return { ...counts, total, rate }; } interface WarningItem { message: string; level: "warning" | "danger"; } export function AttendanceWarningBanner({ records, }: AttendanceWarningBannerProps): JSX.Element | null { const [dismissed, setDismissed] = useState(false); const warnings = useMemo(() => { const stats = calculateStats(records); const items: WarningItem[] = []; if (stats.absent >= 3) { items.push({ message: `本月缺勤 ${stats.absent} 次,超过预警阈值(3 次)`, level: "danger", }); } if (stats.late >= 3) { items.push({ message: `本月迟到 ${stats.late} 次,超过预警阈值(3 次)`, level: "warning", }); } if (stats.total > 0 && stats.rate < 0.9) { items.push({ message: `本月出勤率偏低(${formatPercent(stats.rate)}),低于 90%`, level: "danger", }); } return items; }, [records]); if (dismissed || warnings.length === 0) { return null; } const hasDanger = warnings.some((w) => w.level === "danger"); return (

{hasDanger ? "考勤异常预警" : "考勤提醒"}

    {warnings.map((w, idx) => (
  • {w.message}
  • ))}
); } export default AttendanceWarningBanner; export { calculateStats };