feat: 解聘补偿左右分栏 — 左侧向导计算,右侧暂存多人补偿合计
- 左侧保持原有5步向导不变 - 右侧暂存列表:保存成功后自动添加,显示每人补偿金/代通知金/双倍工资/合计 - 底部汇总:多人补偿金/代通知金/双倍工资/总计 - 支持单条删除和清空全部 - 移动端隐藏右侧(hidden md:block)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { AlertTriangle, Check, ChevronRight, ChevronLeft, Shield, Info, Calculator, FileText, Printer } from 'lucide-react'
|
||||
import { AlertTriangle, Check, ChevronRight, ChevronLeft, Shield, Info, Calculator, FileText, Printer, Trash2, List } from 'lucide-react'
|
||||
import api from '../lib/api'
|
||||
import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
@@ -58,6 +58,21 @@ export default function Termination() {
|
||||
const [checklist, setChecklist] = useState<Record<string, boolean>>({})
|
||||
const [acknowledgeRisk, setAcknowledgeRisk] = useState(false)
|
||||
const [socialAvgWage, setSocialAvgWage] = useState(0)
|
||||
const [savedItems, setSavedItems] = useState<Array<{
|
||||
employeeId: string
|
||||
name: string
|
||||
department: string
|
||||
reason: string
|
||||
reasonLabel: string
|
||||
terminationDate: string
|
||||
severancePay: number
|
||||
noticePay: number
|
||||
doublePay: number
|
||||
grandTotal: number
|
||||
years: number
|
||||
remainingMonths: number
|
||||
compMonths: number
|
||||
}>>([])
|
||||
|
||||
const { data: employees } = useQuery<RosterEmployee[]>({
|
||||
queryKey: ['roster-for-termination'],
|
||||
@@ -279,6 +294,53 @@ export default function Termination() {
|
||||
})
|
||||
}
|
||||
|
||||
// 保存成功后暂存到右侧列表
|
||||
useEffect(() => {
|
||||
if (saveMutation.isSuccess && costResult && selectedEmployee) {
|
||||
setSavedItems((prev) => {
|
||||
if (prev.some((item) => item.employeeId === employeeId)) {
|
||||
return prev.map((item) =>
|
||||
item.employeeId === employeeId
|
||||
? {
|
||||
employeeId,
|
||||
name: selectedEmployee.name,
|
||||
department: selectedEmployee.department,
|
||||
reason,
|
||||
reasonLabel,
|
||||
terminationDate,
|
||||
severancePay: costResult.severancePay,
|
||||
noticePay: costResult.noticePay,
|
||||
doublePay: costResult.doublePay,
|
||||
grandTotal: costResult.grandTotal,
|
||||
years: costResult.years,
|
||||
remainingMonths: costResult.remainingMonths,
|
||||
compMonths: costResult.compMonths,
|
||||
}
|
||||
: item
|
||||
)
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
employeeId,
|
||||
name: selectedEmployee.name,
|
||||
department: selectedEmployee.department,
|
||||
reason,
|
||||
reasonLabel,
|
||||
terminationDate,
|
||||
severancePay: costResult.severancePay,
|
||||
noticePay: costResult.noticePay,
|
||||
doublePay: costResult.doublePay,
|
||||
grandTotal: costResult.grandTotal,
|
||||
years: costResult.years,
|
||||
remainingMonths: costResult.remainingMonths,
|
||||
compMonths: costResult.compMonths,
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
}, [saveMutation.isSuccess])
|
||||
|
||||
const handleReset = () => {
|
||||
setStep(0)
|
||||
setReason('')
|
||||
@@ -288,10 +350,19 @@ export default function Termination() {
|
||||
setAcknowledgeRisk(false)
|
||||
}
|
||||
|
||||
const totalSeverance = savedItems.reduce((sum, item) => sum + item.severancePay, 0)
|
||||
const totalNotice = savedItems.reduce((sum, item) => sum + item.noticePay, 0)
|
||||
const totalDouble = savedItems.reduce((sum, item) => sum + item.doublePay, 0)
|
||||
const totalGrand = savedItems.reduce((sum, item) => sum + item.grandTotal, 0)
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<h1 className="text-xs font-medium">解聘补偿</h1>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{/* 左侧:向导 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
{/* 进度条 */}
|
||||
<div className="flex items-center gap-1">
|
||||
{STEPS.map((s, i) => (
|
||||
@@ -752,6 +823,84 @@ export default function Termination() {
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
|
||||
{/* 右侧:暂存列表 */}
|
||||
<div className="w-72 shrink-0 hidden md:block">
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="text-xs font-medium flex items-center gap-1.5">
|
||||
<List className="w-4 h-4" />已计算列表
|
||||
</h2>
|
||||
{savedItems.length > 0 && (
|
||||
<Button variant="secondary" size="sm" onClick={() => setSavedItems([])}>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{savedItems.length === 0 ? (
|
||||
<div className="text-center py-6 text-gray-400 text-xs">
|
||||
计算完一人后<br />暂存结果将显示在此
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{savedItems.map((item, i) => (
|
||||
<div key={i} className="border rounded-md p-2 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-xs font-medium">{item.name}</div>
|
||||
<div className="text-xs text-gray-400">{item.department}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSavedItems(savedItems.filter((_, idx) => idx !== i))}
|
||||
className="text-gray-400 hover:text-danger"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 flex justify-between">
|
||||
<span>{item.reasonLabel}</span>
|
||||
<span>{item.years}年{item.remainingMonths}月</span>
|
||||
</div>
|
||||
<div className="text-xs space-y-0.5">
|
||||
{item.severancePay > 0 && (
|
||||
<div className="flex justify-between"><span className="text-gray-400">补偿金</span><span>¥{fmt(item.severancePay)}</span></div>
|
||||
)}
|
||||
{item.noticePay > 0 && (
|
||||
<div className="flex justify-between"><span className="text-gray-400">代通知金</span><span>¥{fmt(item.noticePay)}</span></div>
|
||||
)}
|
||||
{item.doublePay > 0 && (
|
||||
<div className="flex justify-between"><span className="text-gray-400">双倍工资</span><span>¥{fmt(item.doublePay)}</span></div>
|
||||
)}
|
||||
<div className="flex justify-between font-bold border-t pt-0.5"><span>合计</span><span className="text-danger">¥{fmt(item.grandTotal)}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 合计 */}
|
||||
<div className="border-t-2 pt-2 space-y-0.5">
|
||||
<div className="text-xs font-medium text-gray-600">合计({savedItems.length}人)</div>
|
||||
{totalSeverance > 0 && (
|
||||
<div className="flex justify-between text-xs"><span className="text-gray-400">补偿金</span><span>¥{fmt(totalSeverance)}</span></div>
|
||||
)}
|
||||
{totalNotice > 0 && (
|
||||
<div className="flex justify-between text-xs"><span className="text-gray-400">代通知金</span><span>¥{fmt(totalNotice)}</span></div>
|
||||
)}
|
||||
{totalDouble > 0 && (
|
||||
<div className="flex justify-between text-xs"><span className="text-gray-400">双倍工资</span><span>¥{fmt(totalDouble)}</span></div>
|
||||
)}
|
||||
<div className="flex justify-between text-sm font-bold">
|
||||
<span>总计</span>
|
||||
<span className="text-danger">¥{fmt(totalGrand)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user