feat: 解聘补偿左右分栏 — 左侧向导计算,右侧暂存多人补偿合计
- 左侧保持原有5步向导不变 - 右侧暂存列表:保存成功后自动添加,显示每人补偿金/代通知金/双倍工资/合计 - 底部汇总:多人补偿金/代通知金/双倍工资/总计 - 支持单条删除和清空全部 - 移动端隐藏右侧(hidden md:block)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo, useEffect } from 'react'
|
import { useState, useMemo, useEffect } from 'react'
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
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 api from '../lib/api'
|
||||||
import Card from '../components/ui/Card'
|
import Card from '../components/ui/Card'
|
||||||
import Button from '../components/ui/Button'
|
import Button from '../components/ui/Button'
|
||||||
@@ -58,6 +58,21 @@ export default function Termination() {
|
|||||||
const [checklist, setChecklist] = useState<Record<string, boolean>>({})
|
const [checklist, setChecklist] = useState<Record<string, boolean>>({})
|
||||||
const [acknowledgeRisk, setAcknowledgeRisk] = useState(false)
|
const [acknowledgeRisk, setAcknowledgeRisk] = useState(false)
|
||||||
const [socialAvgWage, setSocialAvgWage] = useState(0)
|
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[]>({
|
const { data: employees } = useQuery<RosterEmployee[]>({
|
||||||
queryKey: ['roster-for-termination'],
|
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 = () => {
|
const handleReset = () => {
|
||||||
setStep(0)
|
setStep(0)
|
||||||
setReason('')
|
setReason('')
|
||||||
@@ -288,10 +350,19 @@ export default function Termination() {
|
|||||||
setAcknowledgeRisk(false)
|
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 (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h1 className="text-xs font-medium">解聘补偿</h1>
|
<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">
|
<div className="flex items-center gap-1">
|
||||||
{STEPS.map((s, i) => (
|
{STEPS.map((s, i) => (
|
||||||
@@ -752,6 +823,84 @@ export default function Termination() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user