/** * 员工特殊状态台账页面 * 管理三期(孕期/产期/哺乳期)、工伤、医疗期等特殊状态的跟踪和提醒 */ import { useEffect, useState } from 'react' import { Search, Plus, Edit2, Trash2, AlertTriangle, Clock, Baby, HeartPulse, Activity, X } from 'lucide-react' import api from '../lib/api' import { Input, Select, Label } from '../components/ui/Input' import Button from '../components/ui/Button' interface SpecialStatus { id: string type: string status: string startDate: string | null endDate: string | null actualEndDate: string | null expectedDueDate: string | null maternityLeaveStart: string | null maternityLeaveEnd: string | null nursingEndDate: string | null injuryDate: string | null injuryDescription: string | null certificationDate: string | null certificationNo: string | null disabilityLevel: number | null assessmentDate: string | null medicalMonths: number | null medicalPeriodEnd: string | null description: string | null attachments: any[] | null timeline: any[] | null reminderDate: string | null alertLevel: 'red' | 'yellow' | 'green' employee: { id: string; name: string; department: string; phone: string; gender: string; status: string } } interface Stats { total: number active: number pending: number redAlert: number yellowAlert: number byType: { type: string; label: string; count: number }[] } const TYPE_LABELS: Record = { PREGNANCY: '三期', WORK_INJURY: '工伤', MEDICAL_PERIOD: '医疗期', OTHER: '其他', } const TYPE_ICONS: Record = { PREGNANCY: Baby, WORK_INJURY: Activity, MEDICAL_PERIOD: HeartPulse, OTHER: AlertTriangle, } const TYPE_COLORS: Record = { PREGNANCY: 'bg-pink-50 text-pink-700 border-pink-200', WORK_INJURY: 'bg-red-50 text-red-700 border-red-200', MEDICAL_PERIOD: 'bg-orange-50 text-orange-700 border-orange-200', OTHER: 'bg-gray-50 text-gray-700 border-gray-200', } const STATUS_LABELS: Record = { ACTIVE: '进行中', PENDING: '待处理', RESOLVED: '已结束', } const STATUS_COLORS: Record = { ACTIVE: 'bg-emerald-50 text-emerald-700', PENDING: 'bg-amber-50 text-amber-700', RESOLVED: 'bg-gray-100 text-gray-500', } const ALERT_STYLES: Record = { red: { border: 'border-l-4 border-l-red-500', badge: 'bg-red-100 text-red-700', text: 'text-red-600' }, yellow: { border: 'border-l-4 border-l-amber-500', badge: 'bg-amber-100 text-amber-700', text: 'text-amber-600' }, green: { border: 'border-l-4 border-l-emerald-500', badge: 'bg-emerald-100 text-emerald-700', text: 'text-emerald-600' }, } function formatDate(d: string | null): string { if (!d) return '-' return new Date(d).toLocaleDateString('zh-CN') } function daysUntil(d: string | null): number | null { if (!d) return null const diff = new Date(d).getTime() - Date.now() return Math.ceil(diff / (1000 * 60 * 60 * 24)) } export default function SpecialStatus() { const [list, setList] = useState([]) const [total, setTotal] = useState(0) const [page, setPage] = useState(1) const [pageSize] = useState(20) const [search, setSearch] = useState('') const [typeFilter, setTypeFilter] = useState('') const [statusFilter, setStatusFilter] = useState('') const [loading, setLoading] = useState(true) const [stats, setStats] = useState(null) const [editOpen, setEditOpen] = useState(false) const [editing, setEditing] = useState(null) const [deleteTarget, setDeleteTarget] = useState(null) const [employees, setEmployees] = useState([]) const [form, setForm] = useState(getDefaultForm()) function getDefaultForm() { return { employeeId: '', type: 'PREGNANCY', status: 'ACTIVE', startDate: '', endDate: '', actualEndDate: '', expectedDueDate: '', injuryDate: '', injuryDescription: '', certificationDate: '', certificationNo: '', disabilityLevel: '', assessmentDate: '', medicalMonths: '', description: '', reminderDate: '', } } const fetchList = async () => { setLoading(true) try { const params: any = { page, pageSize } if (search) params.search = search if (typeFilter) params.type = typeFilter if (statusFilter) params.status = statusFilter const res = await api.get('/special-statuses', { params }) as any setList(res.data.list) setTotal(res.data.total) } finally { setLoading(false) } } const fetchStats = async () => { try { const res = await api.get('/special-statuses/stats/overview') as any setStats(res.data) } catch { // 忽略 } } const fetchEmployees = async () => { try { const res = await api.get('/employees', { params: { pageSize: 999 } }) as any setEmployees(res.data.list || []) } catch { // 忽略 } } useEffect(() => { fetchList(); fetchStats() }, [page, typeFilter, statusFilter]) useEffect(() => { setPage(1) }, [search, typeFilter, statusFilter]) const handleOpenCreate = () => { setEditing(null) setForm(getDefaultForm()) setEditOpen(true) fetchEmployees() } const handleOpenEdit = (item: SpecialStatus) => { setEditing(item) setForm({ employeeId: item.employee.id, type: item.type, status: item.status, startDate: item.startDate ? item.startDate.split('T')[0] : '', endDate: item.endDate ? item.endDate.split('T')[0] : '', actualEndDate: item.actualEndDate ? item.actualEndDate.split('T')[0] : '', expectedDueDate: item.expectedDueDate ? item.expectedDueDate.split('T')[0] : '', injuryDate: item.injuryDate ? item.injuryDate.split('T')[0] : '', injuryDescription: item.injuryDescription || '', certificationDate: item.certificationDate ? item.certificationDate.split('T')[0] : '', certificationNo: item.certificationNo || '', disabilityLevel: item.disabilityLevel || '', assessmentDate: item.assessmentDate ? item.assessmentDate.split('T')[0] : '', medicalMonths: item.medicalMonths || '', description: item.description || '', reminderDate: item.reminderDate ? item.reminderDate.split('T')[0] : '', }) setEditOpen(true) fetchEmployees() } const handleSave = async () => { if (!form.employeeId) { alert('请选择员工'); return } try { const data: any = { ...form } // 空字符串转 null Object.keys(data).forEach((k) => { if (data[k] === '') data[k] = null }) if (data.disabilityLevel) data.disabilityLevel = parseInt(data.disabilityLevel) if (data.medicalMonths) data.medicalMonths = parseInt(data.medicalMonths) if (editing) { await api.put(`/special-statuses/${editing.id}`, data) } else { await api.post('/special-statuses', data) } setEditOpen(false) fetchList() fetchStats() } catch (err: any) { alert(err.response?.data?.error?.message || '保存失败') } } const handleDelete = async () => { if (!deleteTarget) return try { await api.delete(`/special-statuses/${deleteTarget.id}`) setDeleteTarget(null) fetchList() fetchStats() } catch (err: any) { alert(err.response?.data?.error?.message || '删除失败') } } const totalPages = Math.ceil(total / pageSize) return (
{/* 标题 */}

特殊状态台账

三期/工伤/医疗期等特殊员工状态跟踪与提醒

{/* 统计卡片 */} {stats && (
总记录
{stats.total}
进行中
{stats.active}
待处理
{stats.pending}
紧急提醒
{stats.redAlert}
即将到期
{stats.yellowAlert}
类型分布
{stats.byType.map((t) => ( {t.label}: {t.count} ))}
)} {/* 筛选栏 */}
setSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && fetchList()} />
{/* 列表 */} {loading ? (
加载中...
) : list.length === 0 ? (
暂无特殊状态记录
) : ( <>
{list.map((item) => { const Icon = TYPE_ICONS[item.type] || AlertTriangle const alert = ALERT_STYLES[item.alertLevel] || ALERT_STYLES.green const reminderDays = daysUntil(item.reminderDate) return (
{/* 头部 */}
{TYPE_LABELS[item.type] || item.type} {STATUS_LABELS[item.status] || item.status}
{/* 员工信息 */}
{item.employee.name} {item.employee.department} {item.employee.status === 'RESIGNED' && ( 已离职 )}
{/* 关键日期 */}
{item.type === 'PREGNANCY' && ( <> {item.expectedDueDate && } {item.maternityLeaveStart && } {item.maternityLeaveEnd && } {item.nursingEndDate && } )} {item.type === 'WORK_INJURY' && ( <> {item.injuryDate && } {item.injuryDescription && } {item.certificationDate ? ( ) : ( )} {item.assessmentDate && } {item.disabilityLevel && } )} {item.type === 'MEDICAL_PERIOD' && ( <> {item.startDate && } {item.medicalMonths && } {item.medicalPeriodEnd && } )} {item.type === 'OTHER' && ( <> {item.startDate && } {item.endDate && } )}
{/* 提醒 */} {item.status === 'ACTIVE' && reminderDays !== null && (
{reminderDays < 0 ? `已过期 ${Math.abs(reminderDays)} 天` : `距提醒日还有 ${reminderDays} 天`}
)} {item.description && (
{item.description}
)}
) })}
{/* 分页 */} {totalPages > 1 && (
{page} / {totalPages}
)} )} {/* 新增/编辑弹窗 */} {editOpen && (
setEditOpen(false)}>
e.stopPropagation()}>

{editing ? '编辑特殊状态' : '新增特殊状态'}

{/* 员工选择 */}
{/* 类型 + 状态 */}
{/* 三期专用字段 */} {form.type === 'PREGNANCY' && (

三期信息

setForm({ ...form, expectedDueDate: e.target.value })} />

填写预产期后,系统将自动计算产假起止和哺乳期截止日期

)} {/* 工伤专用字段 */} {form.type === 'WORK_INJURY' && (

工伤信息

setForm({ ...form, injuryDate: e.target.value })} />
setForm({ ...form, injuryDescription: e.target.value })} placeholder="简要描述伤情" />
setForm({ ...form, certificationDate: e.target.value })} />
setForm({ ...form, certificationNo: e.target.value })} />
setForm({ ...form, disabilityLevel: e.target.value })} placeholder="未鉴定则留空" />
setForm({ ...form, assessmentDate: e.target.value })} />
)} {/* 医疗期专用字段 */} {form.type === 'MEDICAL_PERIOD' && (

医疗期信息

setForm({ ...form, startDate: e.target.value })} />
setForm({ ...form, medicalMonths: e.target.value })} placeholder="如3/6/9/12/24" />

填写开始日期和月数后,系统将自动计算截止日期

)} {/* 其他类型 */} {form.type === 'OTHER' && (
setForm({ ...form, startDate: e.target.value })} />
setForm({ ...form, endDate: e.target.value })} />
)} {/* 通用字段 */}
{form.status === 'RESOLVED' && (
setForm({ ...form, actualEndDate: e.target.value })} />
)}
setForm({ ...form, reminderDate: e.target.value })} />

系统将在此日期前7天和30天分别发出提醒

setForm({ ...form, description: e.target.value })} placeholder="补充说明" />
{/* 操作按钮 */}
)} {/* 删除确认 */} {deleteTarget && (
setDeleteTarget(null)}>
e.stopPropagation()}>

确认删除

删除后无法恢复

确定要删除 {deleteTarget.employee.name}{TYPE_LABELS[deleteTarget.type]} 记录吗?

)}
) } /** 信息行组件 */ function Row({ label, value, valueClass }: { label: string; value: string; valueClass?: string }) { return (
{label} {value}
) }