feat: 解聘补偿左右分栏 — 左侧向导计算,右侧暂存多人补偿合计

- 左侧保持原有5步向导不变
- 右侧暂存列表:保存成功后自动添加,显示每人补偿金/代通知金/双倍工资/合计
- 底部汇总:多人补偿金/代通知金/双倍工资/总计
- 支持单条删除和清空全部
- 移动端隐藏右侧(hidden md:block)
This commit is contained in:
freedakgmail
2026-07-23 17:06:13 +08:00
parent e4dae72411
commit 64cfb2f464
+150 -1
View File
@@ -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>
)
}