Files
TurboHR/frontend/src/pages/CommercialInsurance.tsx
T
freedakgmail 13c9192b0a feat: 商业保险新增员工汇总Tab
- 后端新增 /commercial-insurance/employee-summary 接口,按员工维度汇总商险
- 前端商业保险页面新增Tab切换:方案管理 + 员工汇总
- 员工汇总展示每人参保项、年保费合计、保额合计,底部带总计行
2026-08-05 07:25:14 +08:00

122 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
)
}