feat: 社保公积金版本管理 + 员工基数调整 + 加班费计算优化 + UI组件改进

- SocialInsuranceConfig 版本化(effectiveFrom/effectiveTo/isCurrent/adjustmentDone)
- 社保配置版本管理接口(列表/新建/按月获取/当前版本)
- 员工基数调整:预览全部在职员工、上年月均工资计算建议基数、可编辑表格、确认后批量保存
- payroll.service 按批次月份匹配对应版本社保配置
- risk.service / seed.ts 同步更新
- 前端社保tab重构为版本管理+调整+试算
- 加班费计算三步流程、CSV导入、批次导入
- UI组件、Dashboard、合同、薪酬等页面优化
This commit is contained in:
freedakgmail
2026-07-23 16:32:46 +08:00
parent 2a09d31ccc
commit c618710a52
31 changed files with 2245 additions and 745 deletions
+3 -3
View File
@@ -15,9 +15,9 @@ export default function Button({ variant = 'primary', size = 'md', className, ch
'bg-primary text-white hover:bg-primary-dark': variant === 'primary',
'bg-gray-100 text-gray-700 hover:bg-gray-200': variant === 'secondary',
'bg-danger text-white hover:bg-red-700': variant === 'danger',
'px-3 py-1.5 text-sm': size === 'sm',
'px-4 py-2 text-sm': size === 'md',
'px-6 py-3 text-base': size === 'lg',
'px-2.5 py-1 text-xs': size === 'sm',
'px-3 py-1.5 text-xs': size === 'md',
'px-5 py-2 text-sm': size === 'lg',
},
className,
)}
+1 -1
View File
@@ -3,7 +3,7 @@ import clsx from 'clsx'
export default function Card({ className, children, ...props }: HTMLAttributes<HTMLDivElement>) {
return (
<div className={clsx('bg-white rounded-lg shadow-sm border border-gray-200 p-4', className)} {...props}>
<div className={clsx('bg-white rounded-lg shadow-sm border border-gray-200 p-3', className)} {...props}>
{children}
</div>
)
+5 -5
View File
@@ -12,12 +12,12 @@ interface EmptyStateProps {
export default function EmptyState({ icon, title, description, actionLabel, onAction }: EmptyStateProps) {
return (
<div className="flex flex-col items-center justify-center py-16 text-center">
<div className="text-gray-300 mb-4">
{icon || <Inbox className="w-12 h-12" />}
<div className="flex flex-col items-center justify-center py-10 text-center">
<div className="text-gray-300 mb-3">
{icon || <Inbox className="w-10 h-10" />}
</div>
<h3 className="text-base font-medium text-gray-900 mb-1">{title}</h3>
{description && <p className="text-sm text-gray-500 mb-4">{description}</p>}
<h3 className="text-sm font-medium text-gray-900 mb-1">{title}</h3>
{description && <p className="text-xs text-gray-500 mb-3">{description}</p>}
{actionLabel && onAction && (
<Button onClick={onAction}>{actionLabel}</Button>
)}
+3 -3
View File
@@ -7,7 +7,7 @@ export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputE
<input
ref={ref}
className={clsx(
'w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm',
'w-full px-2.5 py-1.5 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-xs',
className,
)}
{...props}
@@ -22,7 +22,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectHTMLAttributes<HTMLSel
<select
ref={ref}
className={clsx(
'w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm bg-white',
'w-full px-2.5 py-1.5 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-xs bg-white',
className,
)}
{...props}
@@ -34,5 +34,5 @@ export const Select = forwardRef<HTMLSelectElement, SelectHTMLAttributes<HTMLSel
)
export function Label({ children, className }: { children: React.ReactNode; className?: string }) {
return <label className={clsx('block text-sm font-medium text-gray-700 mb-1', className)}>{children}</label>
return <label className={clsx('block text-xs font-medium text-gray-700 mb-0.5', className)}>{children}</label>
}
+4 -4
View File
@@ -29,14 +29,14 @@ export default function Modal({ open, onClose, title, children, className }: Mod
<div className="fixed inset-0 bg-black/40" onClick={onClose} />
<div className={clsx('relative bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto', className)}>
{title && (
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-200">
<h3 className="font-medium text-gray-900">{title}</h3>
<div className="flex items-center justify-between px-4 py-2.5 border-b border-gray-200">
<h3 className="font-medium text-gray-900 text-sm">{title}</h3>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>
)}
<div className="p-5">{children}</div>
<div className="p-4">{children}</div>
</div>
</div>
)
+10 -4
View File
@@ -6,16 +6,22 @@
body {
@apply bg-surface text-gray-900 antialiased;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
}
* {
@apply box-border;
}
h1 { @apply text-base font-semibold; }
h2 { @apply text-sm font-semibold; }
h3 { @apply text-sm font-medium; }
}
@layer components {
.btn {
@apply inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
@apply inline-flex items-center justify-center px-3 py-1.5 rounded font-medium text-xs transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-primary {
@apply btn bg-primary text-white hover:bg-primary-dark;
@@ -27,12 +33,12 @@
@apply btn bg-danger text-white hover:bg-red-700;
}
.card {
@apply bg-white rounded-lg shadow-sm border border-gray-200 p-4;
@apply bg-white rounded-lg shadow-sm border border-gray-200 p-3;
}
.input {
@apply w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm;
@apply w-full px-2.5 py-1.5 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-xs;
}
.label {
@apply block text-sm font-medium text-gray-700 mb-1;
@apply block text-xs font-medium text-gray-700 mb-0.5;
}
}
+9 -9
View File
@@ -31,7 +31,7 @@ export default function AIAssistant() {
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold">AI </h1>
<h1 className="text-xs font-medium">AI </h1>
<div className="flex gap-1 border-b overflow-x-auto">
{tabs.map((t) => {
@@ -40,7 +40,7 @@ export default function AIAssistant() {
<button
key={t.key}
onClick={() => setTab(t.key)}
className={`flex items-center gap-1.5 px-4 py-2 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
className={`flex items-center gap-1.5 px-4 py-2 text-xs font-medium border-b-2 transition-colors whitespace-nowrap ${
tab === t.key ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
@@ -95,7 +95,7 @@ function ChatTab() {
<div ref={scrollRef} className="flex-1 overflow-y-auto space-y-4 pb-4">
{messages.map((msg, i) => (
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] px-4 py-3 rounded-lg text-sm whitespace-pre-wrap ${
<div className={`max-w-[80%] px-4 py-3 rounded-lg text-xs whitespace-pre-wrap ${
msg.role === 'user' ? 'bg-primary text-white' : 'bg-gray-100 text-gray-800'
}`}>
{msg.content || (loading && i === messages.length - 1 ? '思考中...' : '')}
@@ -111,7 +111,7 @@ function ChatTab() {
<button
key={q}
onClick={() => send(q)}
className="px-3 py-1.5 text-sm rounded-full border border-gray-300 text-gray-600 hover:bg-gray-50"
className="px-3 py-1.5 text-xs rounded-full border border-gray-300 text-gray-600 hover:bg-gray-50"
>
{q}
</button>
@@ -167,7 +167,7 @@ function PredictTab() {
<Loader2 className="w-5 h-5 animate-spin" /> ...
</div>
) : (
<div className="text-sm text-gray-700 whitespace-pre-wrap">{result}</div>
<div className="text-xs text-gray-700 whitespace-pre-wrap">{result}</div>
)}
<div className="mt-4">
<Button variant="secondary" size="sm" onClick={fetchPrediction} disabled={loading}></Button>
@@ -204,7 +204,7 @@ function ReviewTab() {
</div>
<Label></Label>
<textarea
className="w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm min-h-[200px] resize-y"
className="w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-xs min-h-[200px] resize-y"
placeholder="粘贴劳动合同文本..."
value={contractText}
onChange={(e) => setContractText(e.target.value)}
@@ -219,7 +219,7 @@ function ReviewTab() {
{result && (
<Card>
<h3 className="font-medium mb-3"></h3>
<div className="text-sm text-gray-700 whitespace-pre-wrap">{result}</div>
<div className="text-xs text-gray-700 whitespace-pre-wrap">{result}</div>
</Card>
)}
</div>
@@ -254,7 +254,7 @@ function CaseTab() {
</div>
<Label></Label>
<textarea
className="w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm min-h-[150px] resize-y"
className="w-full px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-xs min-h-[150px] resize-y"
placeholder="例如:员工入职3个月没签合同,现在要辞退他..."
value={scenario}
onChange={(e) => setScenario(e.target.value)}
@@ -269,7 +269,7 @@ function CaseTab() {
{result && (
<Card>
<h3 className="font-medium mb-3"></h3>
<div className="text-sm text-gray-700 whitespace-pre-wrap">{result}</div>
<div className="text-xs text-gray-700 whitespace-pre-wrap">{result}</div>
</Card>
)}
</div>
+23 -23
View File
@@ -61,15 +61,15 @@ export default function Compensation() {
]
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold"></h1>
<div className="space-y-3">
<h1 className="text-xs font-medium"></h1>
<div className="flex gap-1 border-b">
{tabs.map((t) => (
<button
key={t.key}
onClick={() => setTab(t.key)}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
className={`px-3 py-1.5 text-xs font-medium border-b-2 transition-colors ${
tab === t.key ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
@@ -150,7 +150,7 @@ function SeveranceCalculator() {
<div className="grid md:grid-cols-2 gap-4">
<Card>
<h2 className="font-medium mb-4"></h2>
<div className="space-y-4">
<div className="space-y-3">
<EmployeeSelector employees={employees} selectedId={selectedEmpId} onSelect={handleSelectEmp} />
<div>
<Label></Label>
@@ -189,19 +189,19 @@ function SeveranceCalculator() {
<h2 className="font-medium mb-4 flex items-center gap-2"><Calculator className="w-5 h-5" /></h2>
{result ? (
<div className="space-y-3">
<div className="text-sm text-gray-500"><span className="text-gray-900">{result.reason}</span></div>
<div className="text-sm text-gray-500"><span className="text-gray-900">{result.years}{result.remainingMonths}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{result.reason}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{result.years}{result.remainingMonths}</span></div>
{result.noComp ? (
<div className="px-3 py-2 rounded-md bg-gray-50 text-gray-700 text-sm">
<div className="px-3 py-2 rounded-md bg-gray-50 text-gray-700 text-xs">
{result.reasonNote}
</div>
) : (
<>
<div className="text-sm text-gray-500"><span className="text-gray-900">{result.compMonths}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{result.compMonths}</span></div>
{result.capped && (
<div className="text-sm text-warning"> 312</div>
<div className="text-xs text-warning"> 312</div>
)}
<div className="text-sm text-gray-500"><span className="text-gray-900">¥{fmt(result.wage)}/</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">¥{fmt(result.wage)}/</span></div>
<div className="border-t pt-3 space-y-2">
<div className="flex items-center justify-between">
<span className="font-medium">{result.isIllegal ? '经济补偿金' : '应付金额'}</span>
@@ -211,7 +211,7 @@ function SeveranceCalculator() {
<>
<div className="flex items-center justify-between">
<span className="font-medium text-danger">×2</span>
<span className="text-xl font-bold text-danger">¥{fmt(result.totalPay)}</span>
<span className="text-lg font-bold text-danger">¥{fmt(result.totalPay)}</span>
</div>
<div className="text-xs text-gray-400">{result.compMonths} × ¥{fmt(result.wage)} × 2</div>
</>
@@ -219,7 +219,7 @@ function SeveranceCalculator() {
<>
<div className="flex items-center justify-between">
<span className="font-medium">{result.reason}</span>
<span className="text-xl font-bold text-primary">¥{fmt(result.totalPay)}</span>
<span className="text-lg font-bold text-primary">¥{fmt(result.totalPay)}</span>
</div>
<div className="text-xs text-gray-400">{result.compMonths} × ¥{fmt(result.wage)})
{result.noticePay > 0 && <span className="block"> ¥{fmt(result.noticePay)}</span>}
@@ -228,12 +228,12 @@ function SeveranceCalculator() {
)}
</div>
{result.reasonNote && (
<div className={`flex items-start gap-2 px-3 py-2 rounded-md text-sm ${result.isIllegal ? 'bg-red-50 text-red-700' : 'bg-blue-50 text-blue-700'}`}>
<div className={`flex items-start gap-2 px-3 py-2 rounded-md text-xs ${result.isIllegal ? 'bg-red-50 text-red-700' : 'bg-blue-50 text-blue-700'}`}>
<Info className="w-4 h-4 mt-0.5 shrink-0" />
<span>{result.reasonNote}</span>
</div>
)}
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm">
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs">
<Info className="w-4 h-4 mt-0.5 shrink-0" />
<span>116116</span>
</div>
@@ -241,7 +241,7 @@ function SeveranceCalculator() {
)}
</div>
) : (
<div className="text-gray-400 text-sm"></div>
<div className="text-gray-400 text-xs"></div>
)}
</Card>
</div>
@@ -303,7 +303,7 @@ function DoubleSalaryCalculator() {
<div className="grid md:grid-cols-2 gap-4">
<Card>
<h2 className="font-medium mb-4"></h2>
<div className="space-y-4">
<div className="space-y-3">
<EmployeeSelector employees={employees} selectedId={selectedEmpId} onSelect={handleSelectEmp} />
<div>
<Label></Label>
@@ -333,24 +333,24 @@ function DoubleSalaryCalculator() {
<h2 className="font-medium mb-4 flex items-center gap-2"><AlertCircle className="w-5 h-5 text-warning" /></h2>
{result ? (
<div className="space-y-3">
<div className="text-sm text-gray-500"><span className="text-gray-900">{hireDate}</span></div>
<div className="text-sm text-gray-500"><span className="text-gray-900">{hasContract ? contractDate || '未填写' : '未签订'}</span></div>
<div className="text-sm text-gray-500"><span className="text-gray-900">{result.startDate.toISOString().slice(0, 10)}</span></div>
<div className="text-sm text-gray-500"><span className="text-gray-900">{result.endDate.toISOString().slice(0, 10)}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{hireDate}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{hasContract ? contractDate || '未填写' : '未签订'}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{result.startDate.toISOString().slice(0, 10)}</span></div>
<div className="text-xs text-gray-500"><span className="text-gray-900">{result.endDate.toISOString().slice(0, 10)}</span></div>
<div className="border-t pt-3">
<div className="flex items-center justify-between">
<span className="font-medium"></span>
<span className="text-xl font-bold text-danger">¥{fmt(result.totalPay)}</span>
<span className="text-lg font-bold text-danger">¥{fmt(result.totalPay)}</span>
</div>
<div className="text-xs text-gray-400 mt-1">{result.months} × ¥{fmt(monthlyWage)}</div>
</div>
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm">
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs">
<Info className="w-4 h-4 mt-0.5 shrink-0" />
<span>1211</span>
</div>
</div>
) : (
<div className="text-gray-400 text-sm"></div>
<div className="text-gray-400 text-xs"></div>
)}
</Card>
</div>
+33 -21
View File
@@ -91,7 +91,7 @@ export default function Contracts() {
) : (
<>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<table className="w-full text-xs">
<thead>
<tr className="border-b text-left text-gray-500">
<th className="py-2 px-3 font-medium"></th>
@@ -108,10 +108,19 @@ export default function Contracts() {
<td className="py-3 px-3 text-gray-600">{emp.department}</td>
<td className="py-3 px-3 text-gray-600">{emp.hireDate}</td>
<td className="py-3 px-3">
<div className="flex items-center gap-2">
<Signal level={emp.riskLevel} />
<span>{emp.contractStatusText}</span>
</div>
{(() => {
const tagStyles: Record<string, string> = {
expired: 'bg-red-50 text-danger',
unsigned_over_year: 'bg-red-50 text-danger',
unsigned_over_30: 'bg-red-50 text-danger',
unsigned: 'bg-yellow-50 text-yellow-700',
expiring: 'bg-yellow-50 text-yellow-700',
active: 'bg-green-50 text-safe',
unfixed: 'bg-blue-50 text-blue-700',
}
const style = tagStyles[emp.contractStatus] || 'bg-gray-100 text-gray-600'
return <span className={`px-2 py-0.5 rounded text-xs ${style}`}>{emp.contractStatusText}</span>
})()}
</td>
<td className="py-3 px-3">
<div className="flex gap-1">
@@ -135,7 +144,7 @@ export default function Contracts() {
disabled={page === 1}
onClick={() => setPage(p => p - 1)}
></Button>
<span className="text-sm text-gray-500">{page} / {data.totalPages}</span>
<span className="text-xs text-gray-500">{page} / {data.totalPages}</span>
<Button
variant="secondary"
size="sm"
@@ -221,7 +230,7 @@ function AddEmployeeModal({ open, onClose, onSubmit, loading, error }: {
<Modal open={open} onClose={onClose} title="添加员工">
<div className="space-y-4">
{error && (
<div className="px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">
<div className="px-3 py-2 rounded-md bg-red-50 text-red-700 text-xs">
{error.response?.data?.error?.message || '操作失败'}
</div>
)}
@@ -264,15 +273,15 @@ function AddEmployeeModal({ open, onClose, onSubmit, loading, error }: {
{/* 特殊状态 */}
<div className="flex gap-4">
<label className="flex items-center gap-1.5 text-sm">
<label className="flex items-center gap-1.5 text-xs">
<input type="checkbox" checked={form.isPregnant} onChange={(e) => setForm({ ...form, isPregnant: e.target.checked })} />
/
</label>
<label className="flex items-center gap-1.5 text-sm">
<label className="flex items-center gap-1.5 text-xs">
<input type="checkbox" checked={form.isInMedicalPeriod} onChange={(e) => setForm({ ...form, isInMedicalPeriod: e.target.checked })} />
</label>
<label className="flex items-center gap-1.5 text-sm">
<label className="flex items-center gap-1.5 text-xs">
<input type="checkbox" checked={form.isWorkInjured} onChange={(e) => setForm({ ...form, isWorkInjured: e.target.checked })} />
</label>
@@ -392,7 +401,7 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
return (
<div className="fixed inset-0 z-50 flex justify-end">
<div className="fixed inset-0 bg-black/40" onClick={onClose} />
<div className="relative w-full max-w-md bg-white h-full overflow-y-auto shadow-xl">
<div className="relative w-full max-w-2xl bg-white h-full overflow-y-auto shadow-xl">
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-200 sticky top-0 bg-white z-10">
<h3 className="font-medium text-gray-900"></h3>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
@@ -406,9 +415,9 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
<div className="space-y-2">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold">{emp.name}</h2>
<span className="text-sm text-gray-500">{emp.department}</span>
<span className="text-xs text-gray-500">{emp.department}</span>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="grid grid-cols-2 gap-2 text-xs">
<div><span className="text-gray-400"></span>{emp.hireDate?.slice(0, 10)}</div>
<div><span className="text-gray-400"></span>{emp.gender || '-'}</div>
<div><span className="text-gray-400"></span>{emp.phone || '-'}</div>
@@ -425,13 +434,16 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
{emp.contracts && emp.contracts.length > 0 && (
<div className="border-t pt-3">
<h3 className="font-medium text-sm mb-2"></h3>
<div className="space-y-2 text-sm">
<h3 className="font-medium text-xs mb-2"></h3>
<div className="space-y-2 text-xs">
{emp.contracts.map((c: any) => (
<div key={c.id} className="bg-gray-50 rounded p-2">
<div className="flex items-center gap-2">
<Signal level={c.riskLevel || 'safe'} />
<span>{c.contractType === 'FIXED' ? '固定期限' : c.contractType === 'UNFIXED' ? '无固定期限' : '未签'}</span>
{(() => {
const typeLabel = c.contractType === 'FIXED' ? '固定期限' : c.contractType === 'UNFIXED' ? '无固定期限' : '未签'
const typeStyle = c.contractType === 'UNSIGNED' ? 'bg-red-50 text-danger' : 'bg-blue-50 text-blue-700'
return <span className={`px-2 py-0.5 rounded text-xs ${typeStyle}`}>{typeLabel}</span>
})()}
</div>
<div className="text-gray-500 text-xs mt-1">
{c.startDate?.slice(0, 10)} ~ {c.endDate?.slice(0, 10) || '无固定期限'}
@@ -446,13 +458,13 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
<div className="border-t pt-3">
<div className="flex items-center justify-between mb-3">
<h3 className="font-medium text-sm flex items-center gap-1">
<h3 className="font-medium text-xs flex items-center gap-1">
<Paperclip className="w-4 h-4" />
</h3>
</div>
<div className="flex gap-2 mb-3">
<Select value={fileType} onChange={(e) => setFileType(e.target.value as any)} className="text-sm">
<Select value={fileType} onChange={(e) => setFileType(e.target.value as any)} className="text-xs">
<option value="ID_CARD"></option>
<option value="BANK_CARD"></option>
<option value="CONTRACT_SCAN"></option>
@@ -478,7 +490,7 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
{attachments && attachments.length > 0 ? (
<div className="space-y-2">
{attachments.map((att: any) => (
<div key={att.id} className="flex items-center justify-between bg-gray-50 rounded p-2 text-sm">
<div key={att.id} className="flex items-center justify-between bg-gray-50 rounded p-2 text-xs">
<div className="flex items-center gap-2 min-w-0">
<Paperclip className="w-4 h-4 text-gray-400 shrink-0" />
<div className="min-w-0">
@@ -498,7 +510,7 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
))}
</div>
) : (
<div className="text-gray-400 text-sm text-center py-4"></div>
<div className="text-gray-400 text-xs text-center py-4"></div>
)}
</div>
</div>
+53 -53
View File
@@ -103,11 +103,11 @@ export default function Dashboard() {
]
return (
<div className="space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between">
<div>
<h1 className="text-lg font-semibold">{data.greeting}</h1>
<p className="text-sm text-gray-500 mt-0.5">{payroll?.month} </p>
<h1 className="text-xs font-medium">{data.greeting}</h1>
<p className="text-xs text-gray-500 mt-0.5">{payroll?.month} </p>
</div>
<Button variant="secondary" size="sm" onClick={() => refetch()} disabled={isFetching}>
<RefreshCw className={`w-4 h-4 mr-1 ${isFetching ? 'animate-spin' : ''}`} />
@@ -123,7 +123,7 @@ export default function Dashboard() {
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`flex items-center gap-1.5 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors ${
activeTab === tab.key ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
@@ -141,16 +141,16 @@ export default function Dashboard() {
{/* 概览 Tab */}
{activeTab === 'overview' && (
<div className="space-y-4">
<div className="space-y-3">
{/* 统计卡片 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{stats.map((stat) => {
const Icon = stat.icon
return (
<Card key={stat.label} className="flex items-center gap-3">
<Icon className={`w-8 h-8 ${stat.color}`} />
<Card key={stat.label} className="flex items-center gap-2.5">
<Icon className={`w-6 h-6 ${stat.color}`} />
<div>
<div className="text-xl font-bold">{stat.value}</div>
<div className="text-base font-bold">{stat.value}</div>
<div className="text-xs text-gray-500">{stat.label}</div>
</div>
</Card>
@@ -160,17 +160,17 @@ export default function Dashboard() {
{/* 本月工作动态 */}
<Card>
<div className="flex items-center justify-between mb-4">
<h2 className="font-semibold flex items-center gap-2"><Briefcase className="w-5 h-5" /></h2>
<span className="text-sm text-gray-400">{activities?.month}</span>
<div className="flex items-center justify-between mb-3">
<h2 className="font-medium flex items-center gap-1.5"><Briefcase className="w-4 h-4" /></h2>
<span className="text-xs text-gray-400">{activities?.month}</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-2">
{activityItems.map((item) => {
const Icon = item.icon
return (
<div key={item.label} className="flex flex-col items-center p-3 rounded-lg bg-gray-50">
<Icon className={`w-5 h-5 mb-1 ${item.color}`} />
<div className="text-lg font-bold">{item.value}</div>
<div key={item.label} className="flex flex-col items-center p-2 rounded-lg bg-gray-50">
<Icon className={`w-4 h-4 mb-1 ${item.color}`} />
<div className="text-xs font-bold">{item.value}</div>
<div className="text-xs text-gray-500">{item.label}</div>
</div>
)
@@ -180,18 +180,18 @@ export default function Dashboard() {
{/* 风险分布 */}
<Card>
<h2 className="font-semibold mb-4"></h2>
<div className="grid grid-cols-3 gap-4">
<h2 className="font-medium mb-3"></h2>
<div className="grid grid-cols-3 gap-3">
<div className="text-center">
<div className="text-2xl font-bold text-primary">{data.riskDistribution.contract}</div>
<div className="text-lg font-bold text-primary">{data.riskDistribution.contract}</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-warning">{data.riskDistribution.salary}</div>
<div className="text-lg font-bold text-warning">{data.riskDistribution.salary}</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-danger">{data.riskDistribution.termination}</div>
<div className="text-lg font-bold text-danger">{data.riskDistribution.termination}</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
</div>
@@ -202,18 +202,18 @@ export default function Dashboard() {
{/* 薪税 Tab */}
{activeTab === 'payroll' && (
<Card>
<div className="flex items-center justify-between mb-4">
<h2 className="font-semibold flex items-center gap-2"><Calculator className="w-5 h-5" /></h2>
<Link to="/money" className="text-sm text-primary hover:underline flex items-center gap-1">
<div className="flex items-center justify-between mb-3">
<h2 className="font-medium flex items-center gap-1.5"><Calculator className="w-4 h-4" /></h2>
<Link to="/money" className="text-xs text-primary hover:underline flex items-center gap-1">
<ArrowRight className="w-3 h-3" />
</Link>
</div>
{payroll && payroll.payslipCount > 0 ? (
<div className="space-y-4">
<div className="space-y-3">
{/* 工资构成 */}
<div>
<div className="text-sm font-medium text-gray-600 mb-2"></div>
<div className="text-xs font-medium text-gray-600 mb-1.5"></div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{payrollItems.map((item) => {
const Icon = item.icon
@@ -223,7 +223,7 @@ export default function Dashboard() {
<Icon className={`w-4 h-4 ${item.color}`} />
<span className="text-xs text-gray-500">{item.label}</span>
</div>
<span className={`text-sm font-medium ${item.value < 0 ? 'text-danger' : ''}`}>{fmt(item.value)}</span>
<span className={`text-xs font-medium ${item.value < 0 ? 'text-danger' : ''}`}>{fmt(item.value)}</span>
</div>
)
})}
@@ -233,17 +233,17 @@ export default function Dashboard() {
{/* 应发合计 */}
<div className="flex items-center justify-between border-t border-b py-2">
<span className="font-medium"></span>
<span className="text-lg font-bold text-primary">{fmt(payroll.totalPay)}</span>
<span className="text-base font-bold text-primary">{fmt(payroll.totalPay)}</span>
</div>
{/* 扣减项 */}
<div>
<div className="text-sm font-medium text-gray-600 mb-2"></div>
<div className="text-xs font-medium text-gray-600 mb-1.5"></div>
<div className="grid grid-cols-3 gap-2">
{deductionItems.map((item) => (
<div key={item.label} className="flex items-center justify-between p-2 rounded-md bg-red-50">
<span className="text-xs text-gray-500">{item.label}</span>
<span className="text-sm font-medium text-danger">{fmt(item.value)}</span>
<span className="text-xs font-medium text-danger">{fmt(item.value)}</span>
</div>
))}
</div>
@@ -252,40 +252,40 @@ export default function Dashboard() {
{/* 员工实发 */}
<div className="flex items-center justify-between py-2">
<span className="font-medium flex items-center gap-2"><Wallet className="w-4 h-4 text-safe" /></span>
<span className="text-lg font-bold text-safe">{fmt(payroll.empNetPay)}</span>
<span className="text-base font-bold text-safe">{fmt(payroll.empNetPay)}</span>
</div>
{/* 企业成本 */}
<div className="border-t pt-3 space-y-2">
<div className="text-sm font-medium text-gray-600 mb-1"></div>
<div className="border-t pt-2 space-y-2">
<div className="text-xs font-medium text-gray-600 mb-1"></div>
<div className="grid grid-cols-3 gap-2">
<div className="flex items-center justify-between p-2 rounded-md bg-blue-50">
<span className="text-xs text-gray-500 flex items-center gap-1"><Building2 className="w-3 h-3" /></span>
<span className="text-sm font-medium text-blue-700">{fmt(payroll.socialOrg)}</span>
<span className="text-xs font-medium text-blue-700">{fmt(payroll.socialOrg)}</span>
</div>
<div className="flex items-center justify-between p-2 rounded-md bg-purple-50">
<span className="text-xs text-gray-500 flex items-center gap-1"><Building2 className="w-3 h-3" /></span>
<span className="text-sm font-medium text-purple-700">{fmt(payroll.housingOrg)}</span>
<span className="text-xs font-medium text-purple-700">{fmt(payroll.housingOrg)}</span>
</div>
<div className="flex items-center justify-between p-2 rounded-md bg-green-50">
<span className="text-xs text-gray-500 flex items-center gap-1"><Receipt className="w-3 h-3" /></span>
<span className="text-sm font-medium text-green-700">{fmt(payroll.totalPay)}</span>
<span className="text-xs font-medium text-green-700">{fmt(payroll.totalPay)}</span>
</div>
</div>
{payroll.severancePay > 0 && (
<div className="flex items-center justify-between p-2 rounded-md bg-orange-50">
<span className="text-xs text-gray-500 flex items-center gap-1"><DollarSign className="w-3 h-3" /></span>
<span className="text-sm font-medium text-orange-700">{fmt(payroll.severancePay)}</span>
<span className="text-xs font-medium text-orange-700">{fmt(payroll.severancePay)}</span>
</div>
)}
<div className="flex items-center justify-between py-2">
<span className="font-medium flex items-center gap-2"><DollarSign className="w-4 h-4 text-danger" /></span>
<span className="text-lg font-bold text-danger">{fmt(payroll.orgTotalCost)}</span>
<span className="text-base font-bold text-danger">{fmt(payroll.orgTotalCost)}</span>
</div>
</div>
{/* 工资条确认状态 */}
<div className="flex items-center gap-4 text-sm border-t pt-3">
<div className="flex items-center gap-3 text-xs border-t pt-2">
<span className="text-gray-500"></span>
<span className="text-safe"> {payroll.confirmedPayslips}</span>
<span className="text-warning"> {payroll.unconfirmedPayslips}</span>
@@ -300,12 +300,12 @@ export default function Dashboard() {
{/* 风险提醒 Tab */}
{(activeTab === 'risk' || activeTab === 'task') && (
<div className="space-y-4">
<div className="space-y-3">
{/* 待办列表 */}
<Card>
<div className="flex items-center justify-between mb-4">
<h2 className="font-semibold">{activeTab === 'risk' ? '风险提醒' : '月度任务'}</h2>
<span className="text-sm text-gray-400">{filteredTodos.length} </span>
<div className="flex items-center justify-between mb-3">
<h2 className="font-medium">{activeTab === 'risk' ? '风险提醒' : '月度任务'}</h2>
<span className="text-xs text-gray-400">{filteredTodos.length} </span>
</div>
{filteredTodos.length === 0 ? (
@@ -317,12 +317,12 @@ export default function Dashboard() {
{filteredTodos.slice((todoPage - 1) * todoPageSize, todoPage * todoPageSize).map((todo) => (
<div
key={todo.id}
className="flex items-center justify-between px-3 py-3 rounded-md hover:bg-gray-50 transition-colors"
className="flex items-center justify-between px-2.5 py-2 rounded-md hover:bg-gray-50 transition-colors"
>
<Link to={todo.actionUrl} className="flex items-center gap-3 flex-1">
<Link to={todo.actionUrl} className="flex items-center gap-2.5 flex-1">
<TodoIcon type={todo.type} level={todo.level} />
<div className="flex flex-col">
<span className="text-sm text-gray-800">{todo.title}</span>
<span className="text-xs text-gray-800">{todo.title}</span>
<span className="text-xs text-gray-400 flex items-center gap-1"><Clock className="w-3 h-3" />{todo.description}</span>
</div>
</Link>
@@ -354,20 +354,20 @@ export default function Dashboard() {
{/* 已办事项 */}
{data.resolvedTodos && data.resolvedTodos.length > 0 && (
<Card>
<div className="flex items-center justify-between mb-4">
<h2 className="font-semibold flex items-center gap-2"><CheckSquare className="w-5 h-5 text-safe" /></h2>
<span className="text-sm text-gray-400">{data.resolvedTodos.length} </span>
<div className="flex items-center justify-between mb-3">
<h2 className="font-medium flex items-center gap-1.5"><CheckSquare className="w-4 h-4 text-safe" /></h2>
<span className="text-xs text-gray-400">{data.resolvedTodos.length} </span>
</div>
<div className="space-y-2">
<div className="space-y-1.5">
{data.resolvedTodos.map((todo) => (
<div
key={todo.id}
className="flex items-center justify-between px-3 py-3 rounded-md bg-gray-50"
className="flex items-center justify-between px-2.5 py-2 rounded-md bg-gray-50"
>
<Link to={todo.actionUrl} className="flex items-center gap-3 flex-1">
<Link to={todo.actionUrl} className="flex items-center gap-2.5 flex-1">
<TodoIcon type={todo.type} level={todo.level} />
<div className="flex flex-col">
<span className="text-sm text-gray-600 line-through">{todo.title}</span>
<span className="text-xs text-gray-600 line-through">{todo.title}</span>
<span className="text-xs text-gray-400">{todo.description}</span>
</div>
</Link>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -40,8 +40,8 @@ export default function Settings() {
]
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold"></h1>
<div className="space-y-3">
<h1 className="text-xs font-medium"></h1>
<div className="flex gap-1 border-b">
{sections.map((s) => {
@@ -50,7 +50,7 @@ export default function Settings() {
<button
key={s.key}
onClick={() => setActiveSection(s.key)}
className={`flex items-center gap-1.5 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors ${
activeSection === s.key ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
@@ -82,7 +82,7 @@ function OrgSettings({ orgData, onSave, saving }: { orgData: any; onSave: (data:
return (
<Card>
<h2 className="font-medium mb-4"></h2>
<div className="space-y-4 max-w-md">
<div className="space-y-3">
<div>
<Label></Label>
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="企业名称" />
@@ -126,7 +126,7 @@ function UserSettings({ usersData }: { usersData: any }) {
</Button>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<table className="w-full text-xs">
<thead>
<tr className="border-b text-left text-gray-500">
<th className="py-2 px-3 font-medium"></th>
@@ -176,8 +176,8 @@ function AddUserModal({ open, onClose }: { open: boolean; onClose: () => void })
return (
<Modal open={open} onClose={onClose} title="添加用户">
<div className="space-y-4">
{error && <div className="px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">{error}</div>}
<div className="space-y-3">
{error && <div className="px-3 py-2 rounded-md bg-red-50 text-red-700 text-xs">{error}</div>}
<div>
<Label> *</Label>
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
@@ -225,17 +225,17 @@ function PlanSettings({ orgData }: { orgData: any }) {
<Card key={p.key}>
<div className={`px-4 py-3 rounded-t-lg ${plan === p.key ? 'bg-primary text-white' : 'bg-gray-50'}`}>
<div className="font-medium">{p.label}</div>
<div className={`text-lg font-bold ${plan === p.key ? 'text-white' : 'text-gray-900'}`}>{p.price}</div>
<div className={`text-base font-bold ${plan === p.key ? 'text-white' : 'text-gray-900'}`}>{p.price}</div>
</div>
<div className="p-4 space-y-2">
{p.features.map((f, i) => (
<div key={i} className="text-sm text-gray-600 flex items-center gap-2">
<div key={i} className="text-xs text-gray-600 flex items-center gap-2">
<span className="text-safe"></span> {f}
</div>
))}
<div className="pt-2">
{plan === p.key ? (
<div className="text-sm text-center text-primary font-medium"></div>
<div className="text-xs text-center text-primary font-medium"></div>
) : (
<Button variant="secondary" className="w-full" size="sm"></Button>
)}
@@ -288,12 +288,12 @@ function NotificationSettings() {
const logs = logsData?.items || []
return (
<div className="space-y-4">
<div className="space-y-3">
<Card>
<h2 className="font-medium mb-4"></h2>
<div className="space-y-4 max-w-md">
<div className="space-y-3">
<label className="flex items-center justify-between">
<span className="text-sm"></span>
<span className="text-xs"></span>
<input type="checkbox" checked={form.contractExpiry ?? true} onChange={(e) => setForm({ ...form, contractExpiry: e.target.checked })} />
</label>
<div>
@@ -301,19 +301,19 @@ function NotificationSettings() {
<Input type="number" value={form.expiryDays ?? 30} onChange={(e) => setForm({ ...form, expiryDays: Number(e.target.value) })} />
</div>
<label className="flex items-center justify-between">
<span className="text-sm"></span>
<span className="text-xs"></span>
<input type="checkbox" checked={form.contractUnsigned ?? true} onChange={(e) => setForm({ ...form, contractUnsigned: e.target.checked })} />
</label>
<label className="flex items-center justify-between">
<span className="text-sm"></span>
<span className="text-xs"></span>
<input type="checkbox" checked={form.overtimeAlert ?? true} onChange={(e) => setForm({ ...form, overtimeAlert: e.target.checked })} />
</label>
<label className="flex items-center justify-between">
<span className="text-sm"></span>
<span className="text-xs"></span>
<input type="checkbox" checked={form.payslipReady ?? true} onChange={(e) => setForm({ ...form, payslipReady: e.target.checked })} />
</label>
<div className="border-t pt-3 space-y-3">
<div className="text-sm font-medium"></div>
<div className="text-xs font-medium"></div>
<div className="text-xs text-gray-400"></div>
<div className="grid grid-cols-2 gap-3">
<div>
@@ -339,7 +339,7 @@ function NotificationSettings() {
<Input value={form.wechatWebhook || ''} onChange={(e) => setForm({ ...form, wechatWebhook: e.target.value || null })} placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." />
</div>
<label className="flex items-center justify-between">
<span className="text-sm"></span>
<span className="text-xs"></span>
<input type="checkbox" checked={form.emailNotify ?? false} onChange={(e) => setForm({ ...form, emailNotify: e.target.checked })} />
</label>
{form.emailNotify && (
@@ -362,12 +362,12 @@ function NotificationSettings() {
</Button>
</div>
{checkResult && (
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm mb-3">{checkResult}</div>
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs mb-3">{checkResult}</div>
)}
{logs.length > 0 ? (
<div className="space-y-2">
{logs.map((log: any) => (
<div key={log.id} className="text-sm border-b last:border-0 py-2">
<div key={log.id} className="text-xs border-b last:border-0 py-2">
<div className="font-medium">{log.title}</div>
<div className="text-gray-500 text-xs mt-0.5">{log.content}</div>
<div className="text-gray-400 text-xs mt-0.5">{new Date(log.createdAt).toLocaleString('zh-CN')}</div>
@@ -375,7 +375,7 @@ function NotificationSettings() {
))}
</div>
) : (
<div className="text-gray-400 text-sm text-center py-4"></div>
<div className="text-gray-400 text-xs text-center py-4"></div>
)}
</Card>
</div>
+39 -39
View File
@@ -275,8 +275,8 @@ export default function Termination() {
}
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold"></h1>
<div className="space-y-3">
<h1 className="text-xs font-medium"></h1>
{/* 进度条 */}
<div className="flex items-center gap-1">
@@ -289,11 +289,11 @@ export default function Termination() {
</div>
<Card>
<div className="mb-2 text-sm text-gray-500">Step {step + 1}/5{STEPS[step]}</div>
<div className="mb-2 text-xs text-gray-500">Step {step + 1}/5{STEPS[step]}</div>
{/* Step 1: 选择员工 */}
{step === 0 && (
<div className="space-y-4">
<div className="space-y-3">
<div>
<Label></Label>
<Select value={employeeId} onChange={(e) => setEmployeeId(e.target.value)}>
@@ -304,7 +304,7 @@ export default function Termination() {
</Select>
</div>
{selectedEmployee && (
<div className="text-sm text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="font-medium">{selectedEmployee.name}{selectedEmployee.department}</div>
<div>{selectedEmployee.hireDate?.toString().slice(0, 10)}</div>
<div>¥{fmt(selectedEmployee.monthlySalary)}</div>
@@ -330,11 +330,11 @@ export default function Termination() {
)}
{profile && suggestions.length > 0 && (
<div className="space-y-2">
<div className="text-sm font-medium">📋 </div>
<div className="text-xs font-medium">📋 </div>
{suggestions.map((s, i) => (
<div
key={i}
className={`px-3 py-2 rounded-md text-sm ${s.reason === '' ? 'bg-red-50 text-red-700' : 'bg-blue-50 text-blue-700'}`}
className={`px-3 py-2 rounded-md text-xs ${s.reason === '' ? 'bg-red-50 text-red-700' : 'bg-blue-50 text-blue-700'}`}
>
<div className="font-medium">{s.label}</div>
<div className="text-xs mt-0.5">{s.why}</div>
@@ -343,17 +343,17 @@ export default function Termination() {
</div>
)}
{employeeId && !profile && (
<div className="text-sm text-gray-400">...</div>
<div className="text-xs text-gray-400">...</div>
)}
</div>
)}
{/* Step 2: 解聘方式 */}
{step === 1 && (
<div className="space-y-4">
<div className="space-y-3">
{suggestions.length > 0 && (
<div className="bg-blue-50 rounded-md p-3 space-y-1">
<div className="text-sm font-medium text-blue-700">💡 </div>
<div className="text-xs font-medium text-blue-700">💡 </div>
{suggestions.filter((s) => s.reason).map((s, i) => (
<div key={i} className="text-xs text-blue-600">
{s.label}{s.why}
@@ -371,7 +371,7 @@ export default function Termination() {
>
<input type="radio" name="reason" value={r.value} checked={reason === r.value} onChange={(e) => setReason(e.target.value)} className="mt-0.5" />
<div className="flex-1">
<div className="text-sm flex items-center gap-2">
<div className="text-xs flex items-center gap-2">
{r.label}
{suggested && <span className="text-xs text-primary font-medium"></span>}
</div>
@@ -392,12 +392,12 @@ export default function Termination() {
{riskAssessment && riskAssessment.warnings.length > 0 && (
<div className="space-y-2">
{riskAssessment.warnings.map((w, i) => (
<div key={i} className="flex items-center gap-2 px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">
<div key={i} className="flex items-center gap-2 px-3 py-2 rounded-md bg-red-50 text-red-700 text-xs">
<AlertTriangle className="w-4 h-4 shrink-0" />
{w}
</div>
))}
<label className="flex items-center gap-2 text-sm px-3 py-2 rounded-md bg-yellow-50 text-yellow-800">
<label className="flex items-center gap-2 text-xs px-3 py-2 rounded-md bg-yellow-50 text-yellow-800">
<input type="checkbox" checked={acknowledgeRisk} onChange={(e) => setAcknowledgeRisk(e.target.checked)} />
</label>
@@ -416,7 +416,7 @@ export default function Termination() {
checked={checklist[item.key] || false}
onChange={(e) => setChecklist({ ...checklist, [item.key]: e.target.checked })}
/>
<span className="text-sm">{item.label}</span>
<span className="text-xs">{item.label}</span>
</label>
))}
</div>
@@ -424,15 +424,15 @@ export default function Termination() {
{/* Step 4: 费用结算 */}
{step === 3 && (
<div className="space-y-4">
<div className="space-y-3">
<div>
<Label></Label>
<Input type="number" value={socialAvgWage} onChange={(e) => setSocialAvgWage(Number(e.target.value) || 0)} placeholder="用于三倍封顶计算" />
</div>
{costResult && (
<div className="space-y-4">
<div className="space-y-3">
{/* 员工概况 */}
<div className="text-sm text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="font-medium">{selectedEmployee?.name}{selectedEmployee?.department}</div>
<div>{costResult.years}{costResult.remainingMonths}</div>
<div>¥{fmt(costResult.wage)}/</div>
@@ -443,7 +443,7 @@ export default function Termination() {
{/* 经济补偿金 / 赔偿金 */}
{costResult.noComp ? (
<div className="px-3 py-2 rounded-md bg-gray-50 text-gray-700 text-sm">
<div className="px-3 py-2 rounded-md bg-gray-50 text-gray-700 text-xs">
</div>
) : (
@@ -452,22 +452,22 @@ export default function Termination() {
<Calculator className="w-4 h-4" />
{costResult.isIllegal ? '违法解除赔偿金' : '经济补偿金'}
</div>
<div className="text-sm text-gray-500">{costResult.cappedMonths}</div>
<div className="text-sm text-gray-500">¥{fmt(costResult.cappedWage)}/</div>
<div className="text-xs text-gray-500">{costResult.cappedMonths}</div>
<div className="text-xs text-gray-500">¥{fmt(costResult.cappedWage)}/</div>
{costResult.isIllegal && (
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500"></span>
<span>¥{fmt(costResult.basePay)}</span>
</div>
)}
<div className="flex items-center justify-between">
<span className="font-medium">{costResult.isIllegal ? '赔偿金(×2' : '补偿金'}</span>
<span className={`text-lg font-bold ${costResult.isIllegal ? 'text-danger' : 'text-primary'}`}>
<span className={`text-base font-bold ${costResult.isIllegal ? 'text-danger' : 'text-primary'}`}>
¥{fmt(costResult.severancePay)}
</span>
</div>
{costResult.noticePay > 0 && (
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500"></span>
<span>¥{fmt(costResult.noticePay)}</span>
</div>
@@ -491,12 +491,12 @@ export default function Termination() {
<AlertTriangle className="w-4 h-4" />
</div>
<div className="text-sm text-gray-500">{costResult.doubleStartDate}</div>
<div className="text-sm text-gray-500">{costResult.doubleEndDate}</div>
<div className="text-sm text-gray-500">{costResult.doubleMonths}</div>
<div className="text-xs text-gray-500">{costResult.doubleStartDate}</div>
<div className="text-xs text-gray-500">{costResult.doubleEndDate}</div>
<div className="text-xs text-gray-500">{costResult.doubleMonths}</div>
<div className="flex items-center justify-between">
<span className="font-medium"></span>
<span className="text-lg font-bold text-warning">¥{fmt(costResult.doublePay)}</span>
<span className="text-base font-bold text-warning">¥{fmt(costResult.doublePay)}</span>
</div>
<div className="text-xs text-gray-400">{costResult.doubleMonths} × ¥{fmt(costResult.wage)}</div>
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-yellow-50 text-yellow-800 text-xs">
@@ -510,12 +510,12 @@ export default function Termination() {
<div className="border-t pt-3">
<div className="flex items-center justify-between">
<span className="font-medium"></span>
<span className="text-xl font-bold text-danger">¥{fmt(costResult.grandTotal)}</span>
<span className="text-lg font-bold text-danger">¥{fmt(costResult.grandTotal)}</span>
</div>
</div>
{!costResult.noComp && (
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm">
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs">
<Info className="w-4 h-4 mt-0.5 shrink-0" />
<span>116116</span>
</div>
@@ -527,18 +527,18 @@ export default function Termination() {
{/* Step 5: 解聘材料 */}
{step === 4 && (
<div className="space-y-4">
<div className="space-y-3">
{saveMutation.isError ? (
<div className="text-center py-8">
<AlertTriangle className="w-12 h-12 text-danger mx-auto" />
<div className="text-danger font-medium mt-2"></div>
<div className="text-sm text-gray-500">{(saveMutation.error as any)?.response?.data?.error?.message || '请稍后重试'}</div>
<div className="text-xs text-gray-500">{(saveMutation.error as any)?.response?.data?.error?.message || '请稍后重试'}</div>
<Button onClick={() => setStep(3)} className="mt-4"></Button>
</div>
) : saveMutation.isPending ? (
<div className="text-center py-8 text-gray-400">...</div>
) : (
<div className="space-y-4">
<div className="space-y-3">
{/* 成功提示 */}
<div className="flex items-center gap-2 text-safe">
<Check className="w-5 h-5" />
@@ -556,11 +556,11 @@ export default function Termination() {
</div>
{/* 1. 解聘通知书 */}
<div className="border rounded-lg p-6 space-y-4 print:shadow-none">
<div className="border rounded-lg p-6 space-y-3 print:shadow-none">
<div className="text-center">
<h2 className="text-lg font-bold"></h2>
<h2 className="text-base font-bold"></h2>
</div>
<div className="text-sm text-gray-700 space-y-3">
<div className="text-xs text-gray-700 space-y-3">
<p><strong>{selectedEmployee?.name}</strong> /</p>
<p>
<strong>{selectedEmployee?.hireDate?.toString().slice(0, 10)}</strong> {selectedEmployee?.department}
@@ -600,7 +600,7 @@ export default function Termination() {
{costResult && (
<div className="border rounded-lg p-4 space-y-2">
<h3 className="font-medium flex items-center gap-2"><Calculator className="w-4 h-4" /></h3>
<div className="text-sm space-y-1">
<div className="text-xs space-y-1">
<div className="flex justify-between"><span></span><span>{costResult.years}{costResult.remainingMonths}</span></div>
<div className="flex justify-between"><span></span><span>¥{fmt(costResult.wage)}/</span></div>
{costResult.capped && <div className="text-warning"> 312</div>}
@@ -623,7 +623,7 @@ export default function Termination() {
{/* 3. 合规检查清单 */}
<div className="border rounded-lg p-4 space-y-2">
<h3 className="font-medium flex items-center gap-2"><Shield className="w-4 h-4" /></h3>
<div className="text-sm space-y-1">
<div className="text-xs space-y-1">
{checklistItems?.map((item) => (
<div key={item.key} className="flex items-center gap-2">
<span className={checklist[item.key] ? 'text-safe' : 'text-danger'}>
@@ -661,7 +661,7 @@ export default function Termination() {
}, {})
return Object.entries(grouped).map(([category, items]) => (
<div key={category} className="space-y-1">
<div className="text-sm font-medium text-gray-700">{category as string}</div>
<div className="text-xs font-medium text-gray-700">{category as string}</div>
{(items as any[]).map((e: any, i: number) => (
<div key={i} className="text-xs text-gray-600 pl-4 border-l-2 border-gray-200 ml-1">
<div className="flex items-center gap-2">
@@ -677,7 +677,7 @@ export default function Termination() {
})()}
</>
) : (
<div className="text-sm text-gray-400">...</div>
<div className="text-xs text-gray-400">...</div>
)}
</div>
</div>
@@ -48,7 +48,7 @@ export default function ContractConfirm() {
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
<div className="max-w-sm w-full text-center">
<Check className="w-16 h-16 text-safe mx-auto mb-4" />
<h1 className="text-lg font-semibold mb-2"></h1>
<h1 className="text-sm font-semibold mb-2"></h1>
<p className="text-sm text-gray-500"> IP </p>
</div>
</div>
@@ -60,7 +60,7 @@ export default function ContractConfirm() {
<div className="max-w-md mx-auto">
<div className="flex items-center gap-2 mb-6">
<PenTool className="w-6 h-6 text-primary" />
<h1 className="text-lg font-semibold"></h1>
<h1 className="text-sm font-semibold"></h1>
</div>
{loading ? (
@@ -74,7 +74,7 @@ export default function ContractConfirm() {
</Card>
) : data ? (
<Card>
<div className="space-y-4">
<div className="space-y-3">
<div className="text-sm text-gray-600">
{data.orgName} {data.employeeName}
</div>
+2 -2
View File
@@ -37,7 +37,7 @@ export default function MyContract() {
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<FileText className="w-6 h-6 text-primary" />
<h1 className="text-lg font-semibold"></h1>
<h1 className="text-sm font-semibold"></h1>
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">{employee.name}</span>
@@ -51,7 +51,7 @@ export default function MyContract() {
) : !contract ? (
<EmptyState title="暂无合同" description="HR 尚未录入您的合同信息" />
) : (
<div className="space-y-4">
<div className="space-y-3">
{/* 到期提醒 */}
{daysToExpire !== null && daysToExpire <= 30 && daysToExpire >= 0 && (
<div className="flex items-center gap-2 px-3 py-2 rounded-md bg-yellow-50 text-yellow-700 text-sm">
+3 -3
View File
@@ -53,7 +53,7 @@ export default function Onboarding() {
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
<div className="max-w-sm w-full text-center">
<Check className="w-16 h-16 text-safe mx-auto mb-4" />
<h1 className="text-lg font-semibold mb-2"></h1>
<h1 className="text-sm font-semibold mb-2"></h1>
<p className="text-sm text-gray-500">HR </p>
</div>
</div>
@@ -65,7 +65,7 @@ export default function Onboarding() {
<div className="max-w-md mx-auto">
<div className="flex items-center gap-2 mb-6">
<ClipboardList className="w-6 h-6 text-primary" />
<h1 className="text-lg font-semibold"></h1>
<h1 className="text-sm font-semibold"></h1>
</div>
{orgName && (
@@ -79,7 +79,7 @@ export default function Onboarding() {
)}
<Card>
<div className="space-y-4">
<div className="space-y-3">
<div>
<Label> *</Label>
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="请输入姓名" />
+2 -2
View File
@@ -42,7 +42,7 @@ export default function Payslip() {
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<DollarSign className="w-6 h-6 text-primary" />
<h1 className="text-lg font-semibold"></h1>
<h1 className="text-sm font-semibold"></h1>
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">{employee.name}</span>
@@ -93,7 +93,7 @@ export default function Payslip() {
<div className="border-t pt-3">
<div className="flex justify-between">
<span className="font-medium"></span>
<span className="text-xl font-bold text-primary">¥{fmt(Number(data.totalPay))}</span>
<span className="text-base font-bold text-primary">¥{fmt(Number(data.totalPay))}</span>
</div>
</div>
+3 -3
View File
@@ -64,7 +64,7 @@ export default function PortalLogin() {
<div className="w-full max-w-sm">
<div className="flex items-center justify-center gap-2 mb-8">
<Building2 className="w-8 h-8 text-primary" />
<span className="text-xl font-bold"> </span>
<span className="text-base font-bold"> </span>
</div>
<div className="card">
@@ -82,7 +82,7 @@ export default function PortalLogin() {
{error && <div className="mb-4 px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">{error}</div>}
{mode === 'password' ? (
<div className="space-y-4">
<div className="space-y-3">
<div>
<Label></Label>
<Input type="tel" placeholder="请输入手机号" value={phone} onChange={(e) => setPhone(e.target.value)} maxLength={11} />
@@ -96,7 +96,7 @@ export default function PortalLogin() {
</Button>
</div>
) : (
<div className="space-y-4">
<div className="space-y-3">
<div>
<Label></Label>
<Input type="tel" placeholder="请输入手机号" value={phone} onChange={(e) => setPhone(e.target.value)} maxLength={11} />
+1 -1
View File
@@ -15,7 +15,7 @@ export default {
surface: '#F8FAFC',
},
maxWidth: {
content: '960px',
content: 'none',
},
},
},