d79e3baa34
- 面包屑导航组件,集成至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
280 lines
11 KiB
TypeScript
280 lines
11 KiB
TypeScript
/**
|
||
* 医疗期计算器
|
||
* 根据员工工龄和地区计算法定医疗期天数
|
||
* 法律依据:《企业职工患病或非因工负伤医疗期规定》(劳部发[1994]479号)
|
||
* 上海特殊规定:沪府发[2015]40号
|
||
*/
|
||
|
||
import { useState } from 'react'
|
||
import { Calculator, HeartPulse, Info } from 'lucide-react'
|
||
import Card from '../../components/ui/Card'
|
||
import Button from '../../components/ui/Button'
|
||
|
||
interface MedicalPeriodResult {
|
||
totalMonths: number
|
||
cumulativeDays: number
|
||
actualDays: number
|
||
endDate: string
|
||
legalBasis: string
|
||
notes: string[]
|
||
}
|
||
|
||
/**
|
||
* 计算医疗期
|
||
* @param workYears 本单位工作年限
|
||
* @param region 地区(上海/全国)
|
||
* @param sickDays 累计病休天数
|
||
* @param startDate 开始病休日期
|
||
*/
|
||
function calculateMedicalPeriod(
|
||
workYears: number,
|
||
region: 'shanghai' | 'national',
|
||
sickDays: number,
|
||
startDate: string,
|
||
): MedicalPeriodResult | null {
|
||
if (!startDate || workYears < 0) return null
|
||
|
||
let totalMonths: number
|
||
let cumulativeDays: number
|
||
let legalBasis: string
|
||
const notes: string[] = []
|
||
|
||
if (region === 'shanghai') {
|
||
// 上海特殊规定:直接按工龄分档
|
||
if (workYears < 1) {
|
||
totalMonths = 3
|
||
cumulativeDays = 6 * 30 // 6个月周期
|
||
legalBasis = '《上海市关于本市劳动者在履行劳动合同期间患病或者非因工负伤的医疗期标准的规定》'
|
||
} else if (workYears < 4) {
|
||
totalMonths = 3
|
||
cumulativeDays = 6 * 30
|
||
legalBasis = '《上海市关于本市劳动者在履行劳动合同期间患病或者非因工负伤的医疗期标准的规定》'
|
||
} else if (workYears < 10) {
|
||
totalMonths = 6
|
||
cumulativeDays = 12 * 30
|
||
legalBasis = '《上海市关于本市劳动者在履行劳动合同期间患病或者非因工负伤的医疗期标准的规定》'
|
||
} else {
|
||
totalMonths = 9
|
||
cumulativeDays = 18 * 30
|
||
legalBasis = '《上海市关于本市劳动者在履行劳动合同期间患病或者非因工负伤的医疗期标准的规定》'
|
||
}
|
||
notes.push('上海地区适用特殊规定,医疗期不按累计病休天数折算')
|
||
} else {
|
||
// 全国通用规定:劳部发[1994]479号
|
||
if (workYears < 5) {
|
||
totalMonths = 3
|
||
cumulativeDays = 6 * 30 // 6个月内累计病休
|
||
legalBasis = '《企业职工患病或非因工负伤医疗期规定》第三条(劳部发[1994]479号)'
|
||
} else if (workYears < 10) {
|
||
totalMonths = 6
|
||
cumulativeDays = 12 * 30 // 12个月内累计病休
|
||
legalBasis = '《企业职工患病或非因工负伤医疗期规定》第三条(劳部发[1994]479号)'
|
||
} else if (workYears < 15) {
|
||
totalMonths = 9
|
||
cumulativeDays = 15 * 30 // 15个月内累计病休
|
||
legalBasis = '《企业职工患病或非因工负伤医疗期规定》第三条(劳部发[1994]479号)'
|
||
} else if (workYears < 20) {
|
||
totalMonths = 12
|
||
cumulativeDays = 18 * 30 // 18个月内累计病休
|
||
legalBasis = '《企业职工患病或非因工负伤医疗期规定》第三条(劳部发[1994]479号)'
|
||
} else {
|
||
totalMonths = 24
|
||
cumulativeDays = 30 * 30 // 30个月内累计病休
|
||
legalBasis = '《企业职工患病或非因工负伤医疗期规定》第三条(劳部发[1994]479号)'
|
||
}
|
||
notes.push(`在 ${cumulativeDays / 30} 个月的累计周期内,病休累计不超过 ${totalMonths} 个月即享有医疗期保护`)
|
||
}
|
||
|
||
// 计算实际可用天数
|
||
const actualDays = Math.max(0, totalMonths * 30 - sickDays)
|
||
|
||
// 计算医疗期结束日期
|
||
const start = new Date(startDate)
|
||
const endDate = new Date(start)
|
||
endDate.setMonth(endDate.getMonth() + totalMonths)
|
||
|
||
notes.push('医疗期内企业不得解除劳动合同(法定情形除外)')
|
||
notes.push('医疗期满后不能从事原工作也不能从事另行安排的工作,企业可提前30天通知或支付代通知金解除')
|
||
|
||
return {
|
||
totalMonths,
|
||
cumulativeDays,
|
||
actualDays,
|
||
endDate: endDate.toISOString().slice(0, 10),
|
||
legalBasis,
|
||
notes,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 医疗期计算器页面
|
||
*/
|
||
export default function MedicalPeriodCalculator() {
|
||
const [region, setRegion] = useState<'national' | 'shanghai'>('national')
|
||
const [workYears, setWorkYears] = useState('')
|
||
const [sickDays, setSickDays] = useState('0')
|
||
const [startDate, setStartDate] = useState('')
|
||
const [result, setResult] = useState<MedicalPeriodResult | null>(null)
|
||
|
||
const handleCalculate = () => {
|
||
const years = parseFloat(workYears) || 0
|
||
const days = parseInt(sickDays) || 0
|
||
const r = calculateMedicalPeriod(years, region, days, startDate)
|
||
setResult(r)
|
||
}
|
||
|
||
const handleReset = () => {
|
||
setRegion('national')
|
||
setWorkYears('')
|
||
setSickDays('0')
|
||
setStartDate('')
|
||
setResult(null)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
<div className="flex items-center gap-2">
|
||
<HeartPulse className="h-5 w-5 text-primary" />
|
||
<h1 className="text-base font-semibold">医疗期计算器</h1>
|
||
</div>
|
||
|
||
<Card>
|
||
<div className="space-y-3">
|
||
{/* 地区选择 */}
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">所在地区</label>
|
||
<select
|
||
value={region}
|
||
onChange={(e) => setRegion(e.target.value as 'national' | 'shanghai')}
|
||
className="w-full px-3 py-2 text-sm border rounded-md focus:outline-none focus:ring-1 focus:ring-primary"
|
||
>
|
||
<option value="national">全国(通用规定)</option>
|
||
<option value="shanghai">上海(特殊规定)</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* 工龄输入 */}
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">本单位工作年限(年)</label>
|
||
<input
|
||
type="number"
|
||
value={workYears}
|
||
onChange={(e) => setWorkYears(e.target.value)}
|
||
placeholder="如:5.5"
|
||
step="0.5"
|
||
min="0"
|
||
className="w-full px-3 py-2 text-sm border rounded-md focus:outline-none focus:ring-1 focus:ring-primary"
|
||
/>
|
||
</div>
|
||
|
||
{/* 病休开始日期 */}
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">开始病休日期</label>
|
||
<input
|
||
type="date"
|
||
value={startDate}
|
||
onChange={(e) => setStartDate(e.target.value)}
|
||
className="w-full px-3 py-2 text-sm border rounded-md focus:outline-none focus:ring-1 focus:ring-primary"
|
||
/>
|
||
</div>
|
||
|
||
{/* 累计病休天数 */}
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">已累计病休天数</label>
|
||
<input
|
||
type="number"
|
||
value={sickDays}
|
||
onChange={(e) => setSickDays(e.target.value)}
|
||
placeholder="0"
|
||
min="0"
|
||
className="w-full px-3 py-2 text-sm border rounded-md focus:outline-none focus:ring-1 focus:ring-primary"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<Button onClick={handleCalculate} className="flex-1">
|
||
<Calculator className="w-4 h-4 mr-1" />
|
||
立即计算
|
||
</Button>
|
||
<Button variant="secondary" onClick={handleReset}>
|
||
重置
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 计算结果 */}
|
||
{result && (
|
||
<Card className="border-primary/20 bg-primary/5">
|
||
<div className="space-y-3">
|
||
<h2 className="text-sm font-medium flex items-center gap-1.5">
|
||
<Info className="w-4 h-4 text-primary" />
|
||
计算结果
|
||
</h2>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="p-3 rounded-lg bg-white">
|
||
<div className="text-xs text-gray-500">法定医疗期</div>
|
||
<div className="text-lg font-bold text-primary">{result.totalMonths} 个月</div>
|
||
</div>
|
||
<div className="p-3 rounded-lg bg-white">
|
||
<div className="text-xs text-gray-500">累计计算周期</div>
|
||
<div className="text-lg font-bold text-primary">{result.cumulativeDays / 30} 个月</div>
|
||
</div>
|
||
<div className="p-3 rounded-lg bg-white">
|
||
<div className="text-xs text-gray-500">剩余可用天数</div>
|
||
<div className={`text-lg font-bold ${result.actualDays > 0 ? 'text-safe' : 'text-danger'}`}>
|
||
{result.actualDays} 天
|
||
</div>
|
||
</div>
|
||
<div className="p-3 rounded-lg bg-white">
|
||
<div className="text-xs text-gray-500">医疗期截止日</div>
|
||
<div className="text-sm font-bold text-gray-800">{result.endDate}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 法律依据 */}
|
||
<div className="p-2 rounded-md bg-amber-50 text-xs text-amber-800">
|
||
<span className="font-medium">法律依据:</span>{result.legalBasis}
|
||
</div>
|
||
|
||
{/* 注意事项 */}
|
||
<div className="space-y-1">
|
||
<div className="text-xs font-medium text-gray-700">注意事项</div>
|
||
{result.notes.map((note, i) => (
|
||
<div key={i} className="flex items-start gap-1.5 text-xs text-gray-600">
|
||
<span className="w-1.5 h-1.5 rounded-full bg-primary mt-1 flex-shrink-0" />
|
||
{note}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
|
||
{/* 工龄分档表 */}
|
||
<Card>
|
||
<h2 className="text-sm font-medium mb-2">医疗期分档表(全国通用)</h2>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-xs">
|
||
<thead>
|
||
<tr className="border-b text-left text-gray-500">
|
||
<th className="py-2 pr-3">工作年限</th>
|
||
<th className="py-2 pr-3">医疗期</th>
|
||
<th className="py-2 pr-3">累计周期</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y">
|
||
<tr><td className="py-2 pr-3">不满 5 年</td><td className="py-2 pr-3">3 个月</td><td className="py-2 pr-3">6 个月</td></tr>
|
||
<tr><td className="py-2 pr-3">5-10 年</td><td className="py-2 pr-3">6 个月</td><td className="py-2 pr-3">12 个月</td></tr>
|
||
<tr><td className="py-2 pr-3">10-15 年</td><td className="py-2 pr-3">9 个月</td><td className="py-2 pr-3">15 个月</td></tr>
|
||
<tr><td className="py-2 pr-3">15-20 年</td><td className="py-2 pr-3">12 个月</td><td className="py-2 pr-3">18 个月</td></tr>
|
||
<tr><td className="py-2 pr-3">20 年以上</td><td className="py-2 pr-3">24 个月</td><td className="py-2 pr-3">30 个月</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|