import { useState, useRef } from "react" import { toast } from "sonner" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import api from "../../lib/api" import Card from "../../components/ui/Card" import Button from "../../components/ui/Button" import { Input, Label, Select } from "../../components/ui/Input" import Modal from "../../components/ui/Modal" import { Users, FileText, AlertTriangle, Calendar, GraduationCap, TrendingUp, Scale, X, Plus, Paperclip, Trash2, Printer, Calculator, Shield, Info, Check, Eye, Download, UserX, UserPlus, Briefcase, FileSignature, DollarSign, Building2, RotateCcw, History } from "lucide-react" import { fmt } from "./shared" /** 考勤/加班/培训合并组件 */ export default function AttendanceOvertimeInfo({ employeeId, attendanceRecords, overtimeRecords, trainingRecords }: { employeeId: string; attendanceRecords: any[]; overtimeRecords: any[]; trainingRecords: any[] }) { const queryClient = useQueryClient() const [subTab, setSubTab] = useState<'attendance' | 'overtime' | 'training'>('attendance') const [showForm, setShowForm] = useState(false) const [form, setForm] = useState({ date: '', checkInTime: '', checkOutTime: '', status: 'NORMAL', lateMinutes: 0, earlyMinutes: 0, workHours: 8, overtimeHours: 0, remark: '' }) const [trainingForm, setTrainingForm] = useState({ trainingDate: '', topic: '', content: '', trainer: '', duration: 1, ackStatus: 'PENDING', ackDate: '', remark: '' }) const createMutation = useMutation({ mutationFn: (data: any) => api.post(`/roster/${employeeId}/attendance`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) }, }) const deleteMutation = useMutation({ mutationFn: (id: string) => api.delete(`/roster/${employeeId}/attendance/${id}`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }), }) const createTrainingMutation = useMutation({ mutationFn: (data: any) => api.post(`/roster/${employeeId}/training`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) }, }) const deleteTrainingMutation = useMutation({ mutationFn: (id: string) => api.delete(`/roster/${employeeId}/training/${id}`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }), }) const ackMap: Record = { PENDING: '待签收', SIGNED: '已签收', REFUSED: '拒绝签收' } const statusMap: Record = { NORMAL: '正常', LATE: '迟到', EARLY_LEAVE: '早退', ABSENT: '旷工', LEAVE: '请假', BUSINESS_TRIP: '出差' } const statusColor: Record = { NORMAL: 'bg-green-50 text-safe', LATE: 'bg-amber-50 text-warning', EARLY_LEAVE: 'bg-amber-50 text-warning', ABSENT: 'bg-red-50 text-danger', LEAVE: 'bg-blue-50 text-blue-600', BUSINESS_TRIP: 'bg-blue-50 text-blue-600' } const totalPay = (overtimeRecords || []).reduce((sum, o) => sum + (o.totalPay || 0), 0) const totalWeekday = (overtimeRecords || []).reduce((sum, o) => sum + (o.weekdayHours || 0), 0) const totalWeekend = (overtimeRecords || []).reduce((sum, o) => sum + (o.weekendHours || 0), 0) const totalHoliday = (overtimeRecords || []).reduce((sum, o) => sum + (o.holidayHours || 0), 0) return (
{subTab === 'attendance' && ( )} {subTab === 'training' && ( )}
{subTab === 'attendance' && ( <> {showForm && (
setForm({ ...form, date: e.target.value })} />
setForm({ ...form, checkInTime: e.target.value })} />
setForm({ ...form, checkOutTime: e.target.value })} />
setForm({ ...form, lateMinutes: Number(e.target.value) })} />
setForm({ ...form, earlyMinutes: Number(e.target.value) })} />
setForm({ ...form, workHours: Number(e.target.value) })} />
setForm({ ...form, overtimeHours: Number(e.target.value) })} />
setForm({ ...form, remark: e.target.value })} />
)} {attendanceRecords?.length === 0 ? (
暂无考勤记录
) : ( {attendanceRecords?.map((a) => ( ))}
日期 签到 签退 状态 工时 加班
{a.date?.toString().slice(0, 10)} {a.checkInTime || '-'} {a.checkOutTime || '-'} {statusMap[a.status] || a.status} {a.workHours}h {a.overtimeHours > 0 ? `${a.overtimeHours}h` : '-'}
)} )} {subTab === 'overtime' && ( <> {overtimeRecords?.length === 0 ? (
暂无加班记录
) : ( {overtimeRecords.map((o) => ( ))}
月份 工作日(h) 休息日(h) 节假日(h) 加班费
{o.month} {o.weekdayHours || '-'} {o.weekendHours || '-'} {o.holidayHours || '-'} ¥{fmt(o.totalPay)}
合计 {totalWeekday} {totalWeekend} {totalHoliday} ¥{fmt(totalPay)}
)} )} {subTab === 'training' && ( <> {showForm && (
setTrainingForm({ ...trainingForm, trainingDate: e.target.value })} />
setTrainingForm({ ...trainingForm, topic: e.target.value })} placeholder="如《员工手册》培训" />
setTrainingForm({ ...trainingForm, content: e.target.value })} />
setTrainingForm({ ...trainingForm, trainer: e.target.value })} />
setTrainingForm({ ...trainingForm, duration: Number(e.target.value) })} />
{trainingForm.ackStatus === 'SIGNED' &&
setTrainingForm({ ...trainingForm, ackDate: e.target.value })} />
}
setTrainingForm({ ...trainingForm, remark: e.target.value })} />
)} {trainingRecords?.length === 0 ? (
暂无培训签收记录
) : trainingRecords?.map((r) => (
{r.trainingDate?.toString().slice(0, 10)} {r.ackStatus === 'SIGNED' ? '已签收' : r.ackStatus === 'REFUSED' ? '拒绝签收' : '待签收'}
{r.topic}
{r.content &&
{r.content}
}
时长 {r.duration}h {r.trainer && 培训人:{r.trainer}} {r.ackDate && 签收日期:{r.ackDate.toString().slice(0, 10)}} {r.remark && 备注:{r.remark}}
))} )}
) }