13c9192b0a
- 后端新增 /commercial-insurance/employee-summary 接口,按员工维度汇总商险 - 前端商业保险页面新增Tab切换:方案管理 + 员工汇总 - 员工汇总展示每人参保项、年保费合计、保额合计,底部带总计行
122 lines
5.7 KiB
TypeScript
122 lines
5.7 KiB
TypeScript
import { useState } from 'react'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import { Shield } from 'lucide-react'
|
||
import PageGuide from '../components/ui/PageGuide'
|
||
import Card from '../components/ui/Card'
|
||
import { commercialInsuranceApi } from '../lib/api-services'
|
||
import CommercialInsuranceTab from './social-insurance/CommercialInsuranceTab'
|
||
|
||
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||
|
||
const INSURANCE_TYPES: Record<string, { label: string; color: string }> = {
|
||
ACCIDENT: { label: '意外伤害险', color: 'bg-orange-50 text-orange-700 border border-orange-200' },
|
||
SUPPLEMENTARY_MEDICAL: { label: '补充医疗保险', color: 'bg-blue-50 text-blue-700 border border-blue-200' },
|
||
EMPLOYER_LIABILITY: { label: '雇主责任险', color: 'bg-purple-50 text-purple-700 border border-purple-200' },
|
||
CRITICAL_ILLNESS: { label: '重大疾病险', color: 'bg-rose-50 text-rose-700 border border-rose-200' },
|
||
GROUP_LIFE: { label: '团体寿险', color: 'bg-teal-50 text-teal-700 border border-teal-200' },
|
||
OTHER: { label: '其他', color: 'bg-gray-50 text-gray-700 border border-gray-200' },
|
||
}
|
||
|
||
export default function CommercialInsurance() {
|
||
const [tab, setTab] = useState<'plans' | 'summary'>('plans')
|
||
|
||
const { data: employeeSummary = [] } = useQuery<any[]>({
|
||
queryKey: ['commercial-insurance-employee-summary'],
|
||
queryFn: async () => {
|
||
return await commercialInsuranceApi.employeeSummary()
|
||
},
|
||
enabled: tab === 'summary',
|
||
})
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="h-5 w-5 text-primary" />
|
||
<div>
|
||
<h1 className="text-base font-semibold">商业保险</h1>
|
||
<p className="mt-1 text-sm text-gray-500">管理商业保险方案、参保人员及保单信息</p>
|
||
</div>
|
||
</div>
|
||
<PageGuide>
|
||
商业保险是社保之外的自愿性补充保障,包括意外伤害险、补充医疗、雇主责任险等。
|
||
点击「新增方案」创建商险计划,选择方案后可查看参保人员列表。
|
||
<span className="text-primary"> 商业保险是系统增值服务模块,支持方案管理、参保人员追踪、保费统计。</span>
|
||
</PageGuide>
|
||
|
||
{/* Tab 切换 */}
|
||
<div className="flex items-center gap-4 border-b">
|
||
{(['plans', 'summary'] as const).map((t) => (
|
||
<button
|
||
key={t}
|
||
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||
tab === t ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'
|
||
}`}
|
||
onClick={() => setTab(t)}
|
||
>
|
||
{t === 'plans' ? '方案管理' : '员工汇总'}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* ========== 方案管理 Tab ========== */}
|
||
{tab === 'plans' && <CommercialInsuranceTab />}
|
||
|
||
{/* ========== 员工汇总 Tab ========== */}
|
||
{tab === 'summary' && (
|
||
<Card>
|
||
{employeeSummary.length === 0 ? (
|
||
<div className="text-center py-8 text-gray-400 text-sm">暂无员工商险数据</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b text-xs text-gray-500">
|
||
<th className="py-2 px-3 text-left">姓名</th>
|
||
<th className="py-2 px-3 text-left">部门</th>
|
||
<th className="py-2 px-3 text-left">保险项</th>
|
||
<th className="py-2 px-3 text-right">年保费合计</th>
|
||
<th className="py-2 px-3 text-right">保额合计</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{employeeSummary.map((e: any) => (
|
||
<tr key={e.employeeId} className="border-b last:border-0 hover:bg-gray-50">
|
||
<td className="py-2 px-3 font-medium">{e.name}</td>
|
||
<td className="py-2 px-3 text-gray-500">{e.department}</td>
|
||
<td className="py-2 px-3">
|
||
<div className="flex flex-wrap gap-1">
|
||
{e.insurances.map((ins: any, i: number) => {
|
||
const typeCfg = INSURANCE_TYPES[ins.type] || INSURANCE_TYPES.OTHER
|
||
return (
|
||
<span key={i} className={`px-1.5 py-0.5 rounded text-xs ${typeCfg.color}`}>
|
||
{ins.planName} · {ins.provider} · ¥{fmt(ins.premium)}
|
||
</span>
|
||
)
|
||
})}
|
||
</div>
|
||
</td>
|
||
<td className="py-2 px-3 text-right font-medium text-primary">¥{fmt(e.totalPremium)}</td>
|
||
<td className="py-2 px-3 text-right font-medium">¥{fmt(e.totalCoverage)}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr className="border-t-2 font-medium">
|
||
<td className="py-2 px-3" colSpan={3}>合计({employeeSummary.length}人)</td>
|
||
<td className="py-2 px-3 text-right text-primary">
|
||
¥{fmt(employeeSummary.reduce((sum: number, e: any) => sum + e.totalPremium, 0))}
|
||
</td>
|
||
<td className="py-2 px-3 text-right">
|
||
¥{fmt(employeeSummary.reduce((sum: number, e: any) => sum + e.totalCoverage, 0))}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|