feat: 系统优化Phase2 - 面包屑导航/侧边栏间距/制度公示阅读签收/模板变量中文化/通知类型补全

- 面包屑导航组件,集成至TopNav header
- 侧边栏菜单分组间距增大,分组间分隔线
- 制度公示员工阅读签收:PolicyReadRecord模型、portal路由、管理端阅读统计
- 修复Policies.tsx民主程序推进bug(字段名/API路径/参数)
- 用工文本模板变量名英文转中文显示
- 通知类型TYPE_LABELS补全(RISK_ALERT/SOCIAL_INS/OVERTIME_ALERT/PAYSLIP_READY)
- 通知示例数据补充
- h2标题统一为text-sm font-medium
- 新增run.md
This commit is contained in:
selfrelease
2026-07-26 20:32:38 +08:00
parent 9cb0d1f63b
commit d79e3baa34
71 changed files with 18561 additions and 3230 deletions
@@ -0,0 +1,140 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
import Modal from "../../components/ui/Modal"
import { Users, FileText, AlertTriangle, Calendar, GraduationCap, TrendingUp, Scale, X, Plus, Paperclip, Trash2, Printer, Calculator, Shield, Info, Check, Eye, Download, UserX, UserPlus, Briefcase, FileSignature, DollarSign, Building2, RotateCcw, History } from "lucide-react"
import { fmt, terminateReasonMap } from "./shared"
import { CityHistoryTab } from "./PayslipSocialInfo"
/** 变更历史Tab:分组显示各类变更记录 */
export default function ChangeHistoryTab({ profile }: { profile: any }) {
const salaryChanges = profile.salaryChanges || []
const departmentRecords = profile.departmentRecords || []
const socialInsRecords = profile.socialInsRecords || []
const housingFundRecords = profile.housingFundRecords || []
const terminations = profile.terminations || []
const changeTypeMap: Record<string, string> = { ONBOARDING: '入职', REHIRE: '重新入职', ADJUST: '调基', TERMINATION: '离职/解聘', SALARY_CHANGE: '调薪', TRANSFER: '调部门', CITY_CHANGE: '城市变更' }
const changeTypeColor: Record<string, string> = { ONBOARDING: 'bg-green-50 text-safe', REHIRE: 'bg-blue-50 text-blue-600', ADJUST: 'bg-amber-50 text-amber-600', TERMINATION: 'bg-red-50 text-danger', SALARY_CHANGE: 'bg-indigo-50 text-indigo-600', TRANSFER: 'bg-purple-50 text-purple-600', CITY_CHANGE: 'bg-cyan-50 text-cyan-600' }
const totalChanges = salaryChanges.length + departmentRecords.length + socialInsRecords.length + housingFundRecords.length + terminations.length
if (totalChanges === 0) {
return <Card><div className="text-center py-8 text-gray-400"></div></Card>
}
return (
<div className="space-y-4">
{/* 薪资变更 */}
{salaryChanges.length > 0 && (
<Card>
<h3 className="text-sm font-medium mb-3 flex items-center gap-2">
<DollarSign className="w-4 h-4 text-indigo-600" />
{salaryChanges.length}
</h3>
<table className="w-full text-sm">
<thead>
<tr className="border-b text-xs text-gray-500">
<th className="py-2 text-left"></th>
<th className="py-2 text-right"></th>
<th className="py-2 text-right"></th>
<th className="py-2 text-right"></th>
<th className="py-2 text-center"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
</tr>
</thead>
<tbody>
{salaryChanges.map((r: any, idx: number) => (
<tr key={idx} className="border-b last:border-0 hover:bg-gray-50">
<td className="py-2 font-medium">{new Date(r.effectiveDate).toLocaleDateString('zh-CN')}</td>
<td className="py-2 text-right text-gray-600">¥{fmt(r.oldSalary)}</td>
<td className="py-2 text-right text-gray-600">¥{fmt(r.newSalary)}</td>
<td className="py-2 text-right font-medium text-primary">{r.newSalary >= r.oldSalary ? '+' : ''}¥{fmt(r.newSalary - r.oldSalary)}</td>
<td className="py-2 text-center"><span className={`px-2 py-0.5 rounded text-xs ${changeTypeColor[r.changeType] || 'bg-gray-100 text-gray-500'}`}>{changeTypeMap[r.changeType] || r.changeType}</span></td>
<td className="py-2 text-gray-400 text-xs">{r.reason || '-'}</td>
<td className="py-2 text-gray-400 text-xs">{r.endMonth || '至今'}</td>
</tr>
))}
</tbody>
</table>
</Card>
)}
{/* 部门变更 */}
{departmentRecords.length > 0 && (
<Card>
<h3 className="text-sm font-medium mb-3 flex items-center gap-2">
<Building2 className="w-4 h-4 text-purple-600" />
{departmentRecords.length}
</h3>
<table className="w-full text-sm">
<thead>
<tr className="border-b text-xs text-gray-500">
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-center"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
</tr>
</thead>
<tbody>
{departmentRecords.map((r: any, idx: number) => (
<tr key={idx} className="border-b last:border-0 hover:bg-gray-50">
<td className="py-2 font-medium">{r.effectiveMonth}</td>
<td className="py-2 text-gray-600">{r.oldDepartment || '无'}</td>
<td className="py-2 font-medium">{r.newDepartment}</td>
<td className="py-2 text-center"><span className={`px-2 py-0.5 rounded text-xs ${changeTypeColor[r.changeType] || 'bg-gray-100 text-gray-500'}`}>{changeTypeMap[r.changeType] || r.changeType}</span></td>
<td className="py-2 text-gray-400 text-xs">{r.reason || '-'}</td>
<td className="py-2 text-gray-400 text-xs">{r.endMonth || '至今'}</td>
</tr>
))}
</tbody>
</table>
</Card>
)}
{/* 参保城市变更(社保+公积金合并) */}
{(socialInsRecords.length > 0 || housingFundRecords.length > 0) && (
<CityHistoryTab socialInsRecords={socialInsRecords} housingFundRecords={housingFundRecords} changeTypeMap={changeTypeMap} changeTypeColor={changeTypeColor} />
)}
{/* 离职/解聘记录 */}
{terminations.length > 0 && (
<Card>
<h3 className="text-sm font-medium mb-3 flex items-center gap-2">
<UserX className="w-4 h-4 text-danger" />
/{terminations.length}
</h3>
<table className="w-full text-sm">
<thead>
<tr className="border-b text-xs text-gray-500">
<th className="py-2 text-left"></th>
<th className="py-2 text-center"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
<th className="py-2 text-left"></th>
</tr>
</thead>
<tbody>
{terminations.map((r: any, idx: number) => (
<tr key={idx} className="border-b last:border-0 hover:bg-gray-50">
<td className="py-2 font-medium">{new Date(r.terminationDate).toLocaleDateString('zh-CN')}</td>
<td className="py-2 text-center"><span className="px-2 py-0.5 rounded text-xs bg-red-50 text-danger">{terminateReasonMap[r.terminationType] || r.terminationType}</span></td>
<td className="py-2 text-gray-600 text-xs">{r.reason || '-'}</td>
<td className="py-2 text-gray-400 text-xs">{r.remark || '-'}</td>
<td className="py-2 text-gray-400 text-xs">{new Date(r.createdAt).toLocaleString('zh-CN')}</td>
</tr>
))}
</tbody>
</table>
</Card>
)}
</div>
)
}