import { useState, useRef } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Plus, Edit2, Trash2, Upload, Download, Search } from 'lucide-react' import { toast } from 'sonner' import { commissionBonusApi, employeeApi } from '../lib/api-services' import { Input, Label, Select } from '../components/ui/Input' import Button from '../components/ui/Button' import Modal from '../components/ui/Modal' import PageGuide from '../components/ui/PageGuide' import Pagination from '../components/ui/Pagination' import { usePageSize } from '../hooks/usePageSize' export default function CommissionBonus() { const queryClient = useQueryClient() const pageSize = usePageSize() const [page, setPage] = useState(1) const [month, setMonth] = useState(new Date().toISOString().slice(0, 7)) const [search, setSearch] = useState('') const [showAdd, setShowAdd] = useState(false) const [editRecord, setEditRecord] = useState(null) const fileRef = useRef(null) // 列表 + 汇总 const { data, isLoading } = useQuery({ queryKey: ['commission-bonus', month], queryFn: () => commissionBonusApi.list(month), }) // 在职员工列表(新增用) const { data: employees } = useQuery({ queryKey: ['employees-active'], queryFn: () => employeeApi.list({ status: 'ACTIVE' }), enabled: showAdd, }) const records = data?.records || [] const summary = data?.summary || { count: 0, totalBonus: 0, totalDeduction: 0, netAmount: 0 } // 搜索过滤 const filtered = records.filter((r: any) => !search || r.employee?.name?.includes(search) || r.employee?.department?.includes(search) ) // 分页 const paged = filtered.slice((page - 1) * pageSize, page * pageSize) // 新增 const addMutation = useMutation({ mutationFn: (data: { employeeId: string; month: string; amount: number; remark?: string }) => commissionBonusApi.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['commission-bonus'] }) setShowAdd(false) toast.success('已添加') }, onError: (err: any) => toast.error(err?.response?.data?.error?.message || '添加失败'), }) // 更新 const updateMutation = useMutation({ mutationFn: (data: { id: string; amount?: number; remark?: string }) => commissionBonusApi.update(data.id, { amount: data.amount, remark: data.remark }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['commission-bonus'] }) setEditRecord(null) toast.success('已更新') }, onError: (err: any) => toast.error(err?.response?.data?.error?.message || '更新失败'), }) // 删除 const deleteMutation = useMutation({ mutationFn: (id: string) => commissionBonusApi.remove(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['commission-bonus'] }) toast.success('已删除') }, onError: (err: any) => toast.error(err?.response?.data?.error?.message || '删除失败'), }) // 导入 const importMutation = useMutation({ mutationFn: (file: File) => commissionBonusApi.import(file, month), onSuccess: (data: any) => { queryClient.invalidateQueries({ queryKey: ['commission-bonus'] }) toast.success(`导入完成:新增 ${data.created},更新 ${data.updated},跳过 ${data.skipped}`) if (data.errors?.length > 0) { toast.error(`错误明细:${data.errors.slice(0, 3).map((e: any) => `第${e.row}行: ${e.message}`).join(';')}`) } }, onError: (err: any) => toast.error(err?.response?.data?.error?.message || '导入失败'), }) const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) importMutation.mutate(file) if (fileRef.current) fileRef.current.value = '' } const fmt = (n: number) => `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` return (

按月管理员工提成奖金/扣款。金额正数为奖金,负数为扣款。数据可用于薪资批次"获取提成奖金"自动填充。

{/* 筛选栏 */}
{ setMonth(e.target.value); setPage(1) }} className="w-40" />
{ setSearch(e.target.value); setPage(1) }} placeholder="员工姓名/部门" className="pl-9" />
{/* 汇总卡片 */}
记录数
{summary.count}
总奖金
{fmt(summary.totalBonus)}
总扣款
{fmt(summary.totalDeduction)}
净额
= 0 ? 'text-gray-900' : 'text-danger'}`}>{fmt(summary.netAmount)}
{/* 列表 */}
{isLoading ? ( ) : paged.length === 0 ? ( ) : paged.map((r: any) => ( ))}
员工 部门 金额 备注 操作
加载中...
{month} 无提成奖金记录
{r.employee?.name || '-'} {r.employee?.idCardNumber &&
{r.employee.idCardNumber}
}
{r.employee?.department || '-'} = 0 ? 'text-safe' : 'text-danger'}`}> {r.amount >= 0 ? '+' : ''}{fmt(r.amount)} {r.remark || '-'}
{filtered.length > pageSize && ( setPage(1)} /> )} {/* 新增弹窗 */} {showAdd && ( setShowAdd(false)} onSubmit={(data) => addMutation.mutate(data)} saving={addMutation.isPending} /> )} {/* 编辑弹窗 */} {editRecord && ( setEditRecord(null)} onSubmit={(data) => updateMutation.mutate({ id: editRecord.id, ...data })} saving={updateMutation.isPending} /> )}
) } function AddModal({ employees, month, onClose, onSubmit, saving }: { employees: any[] month: string onClose: () => void onSubmit: (data: { employeeId: string; month: string; amount: number; remark?: string }) => void saving: boolean }) { const [employeeId, setEmployeeId] = useState('') const [amount, setAmount] = useState('') const [remark, setRemark] = useState('') return (
setAmount(e.target.value)} placeholder="如 5000 或 -200" />
setRemark(e.target.value)} placeholder="选填" />
) } function EditModal({ record, onClose, onSubmit, saving }: { record: any onClose: () => void onSubmit: (data: { amount?: number; remark?: string }) => void saving: boolean }) { const [amount, setAmount] = useState(String(record.amount)) const [remark, setRemark] = useState(record.remark || '') return (
setAmount(e.target.value)} />
setRemark(e.target.value)} />
) }