feat: 完成23项系统优化 - 花名册社保状态列/身份证复制/附件类型扩展, 用工办理姓名检索/直接提交/文书查看/批量证明, 风险中心跳转筛选+批量处理, 日历7/15/35天分组+逾期统计, 考勤单条编辑+按人导出, 工资条查看状态+工资流水导出, 辞职申请附件上传, 交接清单PDF下载, 操作完成下一步引导, 休假审批入口, 人效成本分部门
This commit is contained in:
@@ -6,14 +6,14 @@ import { attachmentApi, employeeApi } from '../../lib/api-services'
|
||||
import Card from "../../components/ui/Card"
|
||||
import Button from "../../components/ui/Button"
|
||||
import { Input, Label, Select } from "../../components/ui/Input"
|
||||
import { AlertTriangle, Paperclip, Trash2, Eye, Download } from "lucide-react"
|
||||
import { AlertTriangle, Paperclip, Trash2, Eye, Download, Copy } from "lucide-react"
|
||||
import { fmt } from "./shared"
|
||||
|
||||
export default function BasicInfo({ profile, employeeId, attachments }: { profile: any; employeeId: string; attachments: any[] }) {
|
||||
const queryClient = useQueryClient()
|
||||
const [editing, setEditing] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [fileType, setFileType] = useState<'ID_CARD' | 'BANK_CARD' | 'EDUCATION' | 'OTHER'>('ID_CARD')
|
||||
const [fileType, setFileType] = useState<'ID_CARD' | 'BANK_CARD' | 'EDUCATION' | 'CERTIFICATE' | 'CONTRACT' | 'PHOTO' | 'OTHER'>('ID_CARD')
|
||||
|
||||
const addAttachmentMutation = useMutation({
|
||||
mutationFn: (data: any) => attachmentApi.add(data),
|
||||
@@ -46,8 +46,8 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
const fileTypeLabels: Record<string, string> = { ID_CARD: '身份证', BANK_CARD: '银行卡', EDUCATION: '学历证书', OTHER: '其他' }
|
||||
const fileTypeColors: Record<string, string> = { ID_CARD: 'bg-blue-50 text-blue-600', BANK_CARD: 'bg-green-50 text-safe', EDUCATION: 'bg-amber-50 text-amber-600', OTHER: 'bg-gray-100 text-gray-500' }
|
||||
const fileTypeLabels: Record<string, string> = { ID_CARD: '身份证', BANK_CARD: '银行卡', EDUCATION: '学历证书', CERTIFICATE: '职业资格证书', CONTRACT: '合同扫描件', PHOTO: '员工照片', OTHER: '其他' }
|
||||
const fileTypeColors: Record<string, string> = { ID_CARD: 'bg-blue-50 text-blue-600', BANK_CARD: 'bg-green-50 text-safe', EDUCATION: 'bg-amber-50 text-amber-600', CERTIFICATE: 'bg-purple-50 text-purple-600', CONTRACT: 'bg-cyan-50 text-cyan-600', PHOTO: 'bg-pink-50 text-pink-600', OTHER: 'bg-gray-100 text-gray-500' }
|
||||
const formatSize = (bytes: number) => {
|
||||
if (!bytes) return '-'
|
||||
if (bytes < 1024) return `${bytes}B`
|
||||
@@ -195,7 +195,23 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
{personalFields.map((f) => (
|
||||
<div key={f.label} className="flex justify-between border-b pb-1.5 text-xs">
|
||||
<span className="text-gray-500 shrink-0">{f.label}</span>
|
||||
<span className="font-medium text-right truncate ml-2">{f.value}</span>
|
||||
<span className="font-medium text-right truncate ml-2 flex items-center gap-1">
|
||||
{f.value}
|
||||
{f.label === '身份证号' && profile.idCardNumber && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-primary transition-colors shrink-0"
|
||||
title="复制身份证号"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(profile.idCardNumber)
|
||||
.then(() => toast.success('已复制身份证号'))
|
||||
.catch(() => toast.error('复制失败'))
|
||||
}}
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -211,6 +227,70 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{profile.socialInsBase != null && (
|
||||
<div className="pt-3 border-t">
|
||||
<h3 className="text-xs font-medium text-gray-600 mb-2">社保与公积金明细</h3>
|
||||
<div className="grid md:grid-cols-2 gap-x-6 gap-y-1 text-xs">
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">社保缴纳基数</span>
|
||||
<span className="font-medium">¥{fmt(profile.socialInsBase)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">公积金缴纳基数</span>
|
||||
<span className="font-medium">¥{fmt(profile.housingFundBase || 0)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">个人养老(8%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.08)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位养老(16%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.16)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">个人医疗(2%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.02)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位医疗(8%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.08)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">个人失业(0.5%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.005)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位失业(0.5%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.005)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位工伤(0.2%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.002)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位生育(0.8%)</span>
|
||||
<span>¥{fmt((profile.socialInsBase || 0) * 0.008)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">个人公积金(7%)</span>
|
||||
<span>¥{fmt((profile.housingFundBase || 0) * 0.07)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b pb-1">
|
||||
<span className="text-gray-500">单位公积金(7%)</span>
|
||||
<span>¥{fmt((profile.housingFundBase || 0) * 0.07)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between font-medium pt-1">
|
||||
<span>个人合计</span>
|
||||
<span className="text-primary">¥{fmt((profile.socialInsBase || 0) * 0.105 + (profile.housingFundBase || 0) * 0.07)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between font-medium pt-1">
|
||||
<span>单位合计</span>
|
||||
<span className="text-primary">¥{fmt((profile.socialInsBase || 0) * 0.255 + (profile.housingFundBase || 0) * 0.07)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mt-1">注:比例为通用参考值,实际比例以当地政策为准</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
@@ -358,7 +438,7 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
{!editing && (
|
||||
<div className="flex gap-2 items-center">
|
||||
<Select value={fileType} onChange={(e) => setFileType(e.target.value as any)} className="text-xs !w-28">
|
||||
<option value="ID_CARD">身份证</option><option value="BANK_CARD">银行卡</option><option value="EDUCATION">学历证书</option><option value="OTHER">其他</option>
|
||||
<option value="ID_CARD">身份证</option><option value="BANK_CARD">银行卡</option><option value="EDUCATION">学历证书</option><option value="CERTIFICATE">职业资格证书</option><option value="CONTRACT">合同扫描件</option><option value="PHOTO">员工照片</option><option value="OTHER">其他</option>
|
||||
</Select>
|
||||
<input ref={fileInputRef} type="file" className="hidden" onChange={handleFileUpload} />
|
||||
<Button size="sm" variant="secondary" onClick={() => fileInputRef.current?.click()} disabled={addAttachmentMutation.isPending} className="shrink-0 whitespace-nowrap">
|
||||
|
||||
Reference in New Issue
Block a user