fix: 优化文档16项问题修复
- 问题1/3: 绩效考核/培训记录员工姓名可点击跳转员工详情页 - 问题2: 离职证明模板支持自定义+员工端下载 - 问题4(P0): 修复工资填写后数据归零问题 - 问题5: 社保添加员工参保信息列表 - 问题6(P0): 商业保险支持为员工参保 - 问题7(P0): 员工福利支持为员工添加福利 - 问题8: 规章制度支持导入Word文档 - 问题9: 文本模板下载Word增加HTML格式 - 问题10: 模板下载变量替换修复(排除token参数) - 问题11(P0): 电子签署发起时员工下拉框有选项 - 问题12: 新增绩效记录添加考评人选项 - 问题13: 违纪记录添加处罚执行细节 - 问题14: 特殊员工列表添加查看详情按钮和姓名链接 - 问题15: 员工福利汇总正确显示参保人员 - 问题16(P0): 证据链验证修复(递归排序key+自动修复历史哈希)
This commit is contained in:
@@ -2,12 +2,13 @@ import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useConfirm } from '../../hooks/useConfirm'
|
||||
import { Plus, Settings as SettingsIcon, X, Shield } from 'lucide-react'
|
||||
import { commercialInsuranceApi } from '../../lib/api-services'
|
||||
import { Plus, Settings as SettingsIcon, X, Shield, UserPlus } from 'lucide-react'
|
||||
import { commercialInsuranceApi, employeeApi } from '../../lib/api-services'
|
||||
import Card from '../../components/ui/Card'
|
||||
import Button from '../../components/ui/Button'
|
||||
import { Input, Label } from '../../components/ui/Input'
|
||||
import { InlineAlert } from '../../components/ui/InlineAlert'
|
||||
import Modal from '../../components/ui/Modal'
|
||||
|
||||
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
|
||||
@@ -36,6 +37,9 @@ export default function CommercialInsuranceTab() {
|
||||
const [editingPlan, setEditingPlan] = useState<any>(null)
|
||||
const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null)
|
||||
const [newPlan, setNewPlan] = useState<any>({ ...DEFAULT_PLAN })
|
||||
const [showEnrollModal, setShowEnrollModal] = useState(false)
|
||||
const [enrollEmployeeIds, setEnrollEmployeeIds] = useState<string[]>([])
|
||||
const [enrollEffectiveFrom, setEnrollEffectiveFrom] = useState(new Date().toISOString().slice(0, 10))
|
||||
|
||||
const { data: plans = [], isLoading } = useQuery<any[]>({
|
||||
queryKey: ['commercial-insurance-plans'],
|
||||
@@ -79,6 +83,27 @@ export default function CommercialInsuranceTab() {
|
||||
},
|
||||
})
|
||||
|
||||
const { data: rosterData } = useQuery<any[]>({
|
||||
queryKey: ['employees-for-commercial-insurance'],
|
||||
queryFn: async () => {
|
||||
return await employeeApi.allLite({ status: 'ACTIVE' })
|
||||
},
|
||||
enabled: showEnrollModal,
|
||||
})
|
||||
|
||||
const enrollMutation = useMutation({
|
||||
mutationFn: async (data: { employeeIds: string[]; effectiveFrom?: string }) =>
|
||||
commercialInsuranceApi.enroll(selectedPlanId!, data) as any,
|
||||
onSuccess: (res: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['commercial-insurance-enrollments'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['commercial-insurance-employee-summary'] })
|
||||
setShowEnrollModal(false)
|
||||
setEnrollEmployeeIds([])
|
||||
toast.success(`已添加 ${res.data?.enrolled || 0} 名员工`)
|
||||
},
|
||||
onError: () => toast.error('参保失败'),
|
||||
})
|
||||
|
||||
const handleEdit = (plan: any) => {
|
||||
setEditingPlan(plan)
|
||||
setNewPlan({ ...plan })
|
||||
@@ -157,7 +182,12 @@ export default function CommercialInsuranceTab() {
|
||||
|
||||
{selectedPlanId && (
|
||||
<Card>
|
||||
<h3 className="text-sm font-medium mb-3">参保人员({enrollments.length}人)</h3>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium">参保人员({enrollments.length}人)</h3>
|
||||
<Button size="sm" onClick={() => { setEnrollEmployeeIds([]); setEnrollEffectiveFrom(new Date().toISOString().slice(0, 10)); setShowEnrollModal(true) }}>
|
||||
<UserPlus className="w-4 h-4 mr-1" />为员工参保
|
||||
</Button>
|
||||
</div>
|
||||
{enrollLoading ? (
|
||||
<div className="text-center py-4 text-gray-400 text-sm">加载中...</div>
|
||||
) : enrollments.length === 0 ? (
|
||||
@@ -197,6 +227,70 @@ export default function CommercialInsuranceTab() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 批量参保 Modal */}
|
||||
{showEnrollModal && (
|
||||
<Modal open={true} onClose={() => setShowEnrollModal(false)} title="批量参保" size="lg">
|
||||
<div className="space-y-3">
|
||||
<InlineAlert type="info">
|
||||
选择需要参保的员工,设置生效日期后点击「确认参保」。
|
||||
</InlineAlert>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="shrink-0">生效日期</Label>
|
||||
<Input type="date" value={enrollEffectiveFrom} onChange={(e) => setEnrollEffectiveFrom(e.target.value)} className="!w-40" />
|
||||
</div>
|
||||
<div className="max-h-80 overflow-y-auto border rounded-md">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="sticky top-0 bg-white">
|
||||
<tr className="border-b text-xs text-gray-500">
|
||||
<th className="py-2 px-3 text-left w-8">
|
||||
<input type="checkbox" checked={enrollEmployeeIds.length === (rosterData?.length || 0) && enrollEmployeeIds.length > 0}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setEnrollEmployeeIds(rosterData?.map((emp: any) => emp.id) || [])
|
||||
} else {
|
||||
setEnrollEmployeeIds([])
|
||||
}
|
||||
}} />
|
||||
</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-left">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rosterData?.map((emp: any) => (
|
||||
<tr key={emp.id} className="border-b last:border-0 hover:bg-gray-50">
|
||||
<td className="py-2 px-3">
|
||||
<input type="checkbox" checked={enrollEmployeeIds.includes(emp.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) setEnrollEmployeeIds([...enrollEmployeeIds, emp.id])
|
||||
else setEnrollEmployeeIds(enrollEmployeeIds.filter((id) => id !== emp.id))
|
||||
}} />
|
||||
</td>
|
||||
<td className="py-2 px-3 font-medium">{emp.name}</td>
|
||||
<td className="py-2 px-3 text-gray-500">{emp.department}</td>
|
||||
<td className="py-2 px-3">
|
||||
<span className="px-2 py-0.5 rounded text-xs bg-green-50 text-safe">在职</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-gray-400">已选 {enrollEmployeeIds.length} 人</span>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={() => setShowEnrollModal(false)}>取消</Button>
|
||||
<Button size="sm" onClick={() => enrollMutation.mutate({ employeeIds: enrollEmployeeIds, effectiveFrom: enrollEffectiveFrom })}
|
||||
disabled={enrollEmployeeIds.length === 0 || enrollMutation.isPending}>
|
||||
{enrollMutation.isPending ? '参保中...' : '确认参保'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{showAddPlan && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30" onClick={() => setShowAddPlan(false)}>
|
||||
<Card className="w-full max-w-lg mx-4" >
|
||||
|
||||
Reference in New Issue
Block a user