fix: 统一风险中心及全应用身份证号显示

后端:
- employee.routes.ts: all-lite 接口加上 idCardNumber 并解密
- special-status.routes.ts: 列表和详情查询加上 idCardNumber 并解密
- esign.routes.ts: 列表/pending/detail/remind 接口加上 idCardNumber 并解密
- risk.service.ts: getMonthlyCalendar 所有事件加上 employeeIdCardNumber
- termination.service.ts: batchTerminatePreview 加上 employeeIdCardNumber
- employee.routes.ts: preview-renew 加上 employeeIdCardNumber

前端:
- Roster.tsx: 批量续签/解聘预检结果加上身份证号
- SpecialStatus.tsx: 列表/下拉/删除确认加上身份证号
- Calendar.tsx: 日历事件姓名后显示身份证号
- ESign.tsx: 签署列表/详情/员工列表/提醒弹窗/下拉加上身份证号
- CommercialInsuranceTab.tsx: 参保选择列表加上身份证号
- OvertimeTab.tsx: 导入预览加上身份证号
This commit is contained in:
freedakgmail
2026-08-18 08:14:26 +08:00
parent 4d78eac5c2
commit eb91c2d8fb
11 changed files with 64 additions and 26 deletions
+7 -2
View File
@@ -56,10 +56,14 @@ router.get('/all-lite', authMiddleware, async (req: AuthRequest, res, next) => {
status: { in: status }, status: { in: status },
...(department && { department }), ...(department && { department }),
}, },
select: { id: true, name: true, department: true, position: true, gender: true, status: true }, select: { id: true, name: true, department: true, position: true, gender: true, status: true, idCardNumber: true },
orderBy: { name: 'asc' }, orderBy: { name: 'asc' },
}) })
res.json({ success: true, data: employees }) const data = employees.map((e: any) => ({
...e,
idCardNumber: (() => { try { const v = e.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
}))
res.json({ success: true, data })
} catch (err) { } catch (err) {
next(err) next(err)
} }
@@ -301,6 +305,7 @@ router.post('/contracts/preview-renew', authMiddleware, async (req: AuthRequest,
contractId: contract.id, contractId: contract.id,
employeeId: contract.employeeId, employeeId: contract.employeeId,
employeeName: employee.name, employeeName: employee.name,
employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
department: employee.department, department: employee.department,
currentContractType: contract.contractType, currentContractType: contract.contractType,
renewalCount, renewalCount,
+20 -9
View File
@@ -20,6 +20,7 @@ import { authMiddleware, AuthRequest } from '../middleware/auth'
import { auditLog } from '../middleware/auditLog' import { auditLog } from '../middleware/auditLog'
import { createEvidence, appendEvidence } from '../services/evidence.service' import { createEvidence, appendEvidence } from '../services/evidence.service'
import { renderTemplate, getTemplateById } from '../services/template.service' import { renderTemplate, getTemplateById } from '../services/template.service'
import { decrypt } from '../lib/crypto'
const router = Router() const router = Router()
router.use(authMiddleware) router.use(authMiddleware)
@@ -69,11 +70,15 @@ router.get('/', async (req: AuthRequest, res: Response, next: NextFunction) => {
...(scene && { scene }), ...(scene && { scene }),
}, },
include: { include: {
employee: { select: { id: true, name: true, department: true, phone: true } }, employee: { select: { id: true, name: true, department: true, phone: true, idCardNumber: true } },
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
}) })
res.json({ success: true, data: records }) const recordsWithIdCard = records.map((r: any) => ({
...r,
employee: r.employee ? { ...r.employee, idCardNumber: (() => { try { const v = r.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : r.employee,
}))
res.json({ success: true, data: recordsWithIdCard })
} catch (err) { next(err) } } catch (err) { next(err) }
}) })
@@ -98,7 +103,7 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
status: { in: ['PENDING', 'SIGNING'] }, status: { in: ['PENDING', 'SIGNING'] },
}, },
include: { include: {
employee: { select: { id: true, name: true, department: true, phone: true, status: true } }, employee: { select: { id: true, name: true, department: true, phone: true, status: true, idCardNumber: true } },
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
}) })
@@ -111,7 +116,7 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
employee: { status: 'ACTIVE' }, employee: { status: 'ACTIVE' },
}, },
include: { include: {
employee: { select: { id: true, name: true, department: true, phone: true, status: true } }, employee: { select: { id: true, name: true, department: true, phone: true, status: true, idCardNumber: true } },
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
}) })
@@ -122,17 +127,19 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
name: string name: string
department: string | null department: string | null
phone: string | null phone: string | null
idCardNumber: string | null
pendingItems: any[] pendingItems: any[]
}>() }>()
// 辅助函数:添加员工到 map // 辅助函数:添加员工到 map
const ensureEmployee = (emp: { id: string; name: string; department: string | null; phone: string | null }) => { const ensureEmployee = (emp: { id: string; name: string; department: string | null; phone: string | null; idCardNumber?: string | null }) => {
if (!employeeMap.has(emp.id)) { if (!employeeMap.has(emp.id)) {
employeeMap.set(emp.id, { employeeMap.set(emp.id, {
employeeId: emp.id, employeeId: emp.id,
name: emp.name, name: emp.name,
department: emp.department, department: emp.department,
phone: emp.phone, phone: emp.phone,
idCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
pendingItems: [], pendingItems: [],
}) })
} }
@@ -287,7 +294,7 @@ router.post('/remind', async (req: AuthRequest, res: Response, next: NextFunctio
} }
const employee = await prisma.employee.findFirst({ const employee = await prisma.employee.findFirst({
where: { id: employeeId, orgId: req.user!.orgId, status: 'ACTIVE' }, where: { id: employeeId, orgId: req.user!.orgId, status: 'ACTIVE' },
select: { id: true, name: true, phone: true, orgId: true }, select: { id: true, name: true, phone: true, orgId: true, idCardNumber: true },
}) })
if (!employee) { if (!employee) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在或已离职' } }) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在或已离职' } })
@@ -300,7 +307,7 @@ router.post('/remind', async (req: AuthRequest, res: Response, next: NextFunctio
) )
const url = `${process.env.PORTAL_BASE_URL || ''}/portal/auto-login?token=${token}&redirect=/portal/esign` const url = `${process.env.PORTAL_BASE_URL || ''}/portal/auto-login?token=${token}&redirect=/portal/esign`
await auditLog(req, 'REMIND', 'ESIGN', employeeId, { employeeName: employee.name }) await auditLog(req, 'REMIND', 'ESIGN', employeeId, { employeeName: employee.name })
res.json({ success: true, data: { url, token, employeeName: employee.name, phone: employee.phone } }) res.json({ success: true, data: { url, token, employeeName: employee.name, employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(), phone: employee.phone } })
} catch (err) { next(err) } } catch (err) { next(err) }
}) })
@@ -413,11 +420,15 @@ router.get('/:id', async (req: AuthRequest, res: Response, next: NextFunction) =
const record = await prisma.eSignRecord.findFirst({ const record = await prisma.eSignRecord.findFirst({
where: { id: req.params.id, orgId: req.user!.orgId }, where: { id: req.params.id, orgId: req.user!.orgId },
include: { include: {
employee: { select: { id: true, name: true, department: true, phone: true } }, employee: { select: { id: true, name: true, department: true, phone: true, idCardNumber: true } },
}, },
}) })
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在' } }) if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在' } })
res.json({ success: true, data: record }) const recordWithIdCard = {
...record,
employee: record.employee ? { ...record.employee, idCardNumber: (() => { try { const v = record.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : record.employee,
}
res.json({ success: true, data: recordWithIdCard })
} catch (err) { next(err) } } catch (err) { next(err) }
}) })
+4 -2
View File
@@ -5,6 +5,7 @@
import { Router, Response, NextFunction } from 'express' import { Router, Response, NextFunction } from 'express'
import { authMiddleware, AuthRequest } from '../middleware/auth' import { authMiddleware, AuthRequest } from '../middleware/auth'
import prisma from '../lib/prisma' import prisma from '../lib/prisma'
import { decrypt } from '../lib/crypto'
import { parsePagination } from '../lib/pagination' import { parsePagination } from '../lib/pagination'
import { import {
createSpecialStatus, createSpecialStatus,
@@ -49,7 +50,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: Ne
take: pageSize, take: pageSize,
include: { include: {
employee: { employee: {
select: { id: true, name: true, department: true, phone: true, gender: true, status: true }, select: { id: true, name: true, department: true, phone: true, gender: true, status: true, idCardNumber: true },
}, },
}, },
}), }),
@@ -60,6 +61,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: Ne
const listWithAlert = list.map((item: any) => ({ const listWithAlert = list.map((item: any) => ({
...item, ...item,
alertLevel: getAlertLevel(item.reminderDate, item.status), alertLevel: getAlertLevel(item.reminderDate, item.status),
employee: item.employee ? { ...item.employee, idCardNumber: (() => { try { const v = item.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : item.employee,
})) }))
res.json({ res.json({
@@ -80,7 +82,7 @@ router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next:
where: { id: req.params.id, orgId: req.user!.orgId }, where: { id: req.params.id, orgId: req.user!.orgId },
include: { include: {
employee: { employee: {
select: { id: true, name: true, department: true, phone: true, gender: true, hireDate: true }, select: { id: true, name: true, department: true, phone: true, gender: true, hireDate: true, idCardNumber: true },
}, },
}, },
}) })
+9 -1
View File
@@ -1072,6 +1072,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: string type: string
title: string title: string
employeeName?: string employeeName?: string
employeeIdCardNumber?: string | null
employeeId?: string employeeId?: string
actionUrl?: string actionUrl?: string
priority: 'high' | 'medium' | 'low' priority: 'high' | 'medium' | 'low'
@@ -1091,6 +1092,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'CONTRACT_EXPIRY', type: 'CONTRACT_EXPIRY',
title: `${c.employee.name} 合同到期`, title: `${c.employee.name} 合同到期`,
employeeName: c.employee.name, employeeName: c.employee.name,
employeeIdCardNumber: (() => { try { const v = c.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: c.employeeId, employeeId: c.employeeId,
actionUrl: '/contracts', actionUrl: '/contracts',
priority: 'high', priority: 'high',
@@ -1114,6 +1116,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'PROBATION_END', type: 'PROBATION_END',
title: `${c.employee.name} 试用期到期`, title: `${c.employee.name} 试用期到期`,
employeeName: c.employee.name, employeeName: c.employee.name,
employeeIdCardNumber: (() => { try { const v = c.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: c.employeeId, employeeId: c.employeeId,
actionUrl: '/roster', actionUrl: '/roster',
priority: 'medium', priority: 'medium',
@@ -1136,6 +1139,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'TERMINATION', type: 'TERMINATION',
title: `${t.employee.name} 离职/解聘日`, title: `${t.employee.name} 离职/解聘日`,
employeeName: t.employee.name, employeeName: t.employee.name,
employeeIdCardNumber: (() => { try { const v = t.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: t.employeeId, employeeId: t.employeeId,
actionUrl: '/termination', actionUrl: '/termination',
priority: 'high', priority: 'high',
@@ -1162,6 +1166,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'ANNIVERSARY', type: 'ANNIVERSARY',
title: `${emp.name} 入职${years}周年`, title: `${emp.name} 入职${years}周年`,
employeeName: emp.name, employeeName: emp.name,
employeeIdCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: emp.id, employeeId: emp.id,
actionUrl: '/roster', actionUrl: '/roster',
priority: 'low', priority: 'low',
@@ -1189,6 +1194,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'RISK_DEADLINE', type: 'RISK_DEADLINE',
title: `${r.title} 处理截止日`, title: `${r.title} 处理截止日`,
employeeName: r.employee?.name, employeeName: r.employee?.name,
employeeIdCardNumber: (() => { try { const v = r.employee?.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: r.employeeId || undefined, employeeId: r.employeeId || undefined,
actionUrl: r.actionUrl || '/', actionUrl: r.actionUrl || '/',
priority: r.level === 'HIGH' ? 'high' : 'medium', priority: r.level === 'HIGH' ? 'high' : 'medium',
@@ -1217,6 +1223,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: 'RETIREMENT', type: 'RETIREMENT',
title: `${emp.name} 达到法定退休年龄`, title: `${emp.name} 达到法定退休年龄`,
employeeName: emp.name, employeeName: emp.name,
employeeIdCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
employeeId: emp.id, employeeId: emp.id,
actionUrl: '/roster', actionUrl: '/roster',
priority: 'high', priority: 'high',
@@ -1251,7 +1258,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
orgId, orgId,
date: { gte: monthStart, lte: monthEnd }, date: { gte: monthStart, lte: monthEnd },
}, },
include: { employee: { select: { name: true } } }, include: { employee: { select: { name: true, idCardNumber: true } } },
}) })
for (const ev of customEvents) { for (const ev of customEvents) {
events.push({ events.push({
@@ -1259,6 +1266,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
type: ev.type, type: ev.type,
title: ev.title + (ev.employee ? `${ev.employee.name}` : ''), title: ev.title + (ev.employee ? `${ev.employee.name}` : ''),
employeeName: ev.employee?.name, employeeName: ev.employee?.name,
employeeIdCardNumber: (() => { try { const v = ev.employee?.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
actionUrl: '/dashboard', actionUrl: '/dashboard',
priority: ev.priority as 'high' | 'medium' | 'low', priority: ev.priority as 'high' | 'medium' | 'low',
}) })
@@ -1,6 +1,7 @@
import prisma from '../lib/prisma' import prisma from '../lib/prisma'
import { RiskAssessment, TerminationReason } from '@prisma/client' import { RiskAssessment, TerminationReason } from '@prisma/client'
import { autoCreateEsignRecord } from './esign.service' import { autoCreateEsignRecord } from './esign.service'
import { decrypt } from '../lib/crypto'
function dateToMonth(date: Date): string { function dateToMonth(date: Date): string {
const y = date.getFullYear() const y = date.getFullYear()
@@ -400,6 +401,7 @@ export function calculateCompensation(hireDate: Date, leaveDate: Date, monthlyWa
export interface BatchTerminatePreview { export interface BatchTerminatePreview {
employeeId: string employeeId: string
employeeName: string employeeName: string
employeeIdCardNumber: string | null
department: string department: string
reason: string reason: string
terminationDate: string terminationDate: string
@@ -423,6 +425,7 @@ export async function batchTerminatePreview(
results.push({ results.push({
employeeId: item.employeeId, employeeId: item.employeeId,
employeeName: '(未找到)', employeeName: '(未找到)',
employeeIdCardNumber: null,
department: '', department: '',
reason: item.reason, reason: item.reason,
terminationDate: item.terminationDate, terminationDate: item.terminationDate,
@@ -437,6 +440,7 @@ export async function batchTerminatePreview(
results.push({ results.push({
employeeId: item.employeeId, employeeId: item.employeeId,
employeeName: employee.name, employeeName: employee.name,
employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
department: employee.department, department: employee.department,
reason: item.reason, reason: item.reason,
terminationDate: item.terminationDate, terminationDate: item.terminationDate,
+1
View File
@@ -377,6 +377,7 @@ export default function Calendar() {
<div className="text-xs text-gray-800 mt-0.5 truncate"> <div className="text-xs text-gray-800 mt-0.5 truncate">
{ev.title} {ev.title}
{ev.employeeName && <span className="text-gray-400 ml-1"> {ev.employeeName}</span>} {ev.employeeName && <span className="text-gray-400 ml-1"> {ev.employeeName}</span>}
{ev.employeeIdCardNumber && <span className="text-gray-400 ml-1 font-mono text-[10px]">{ev.employeeIdCardNumber}</span>}
</div> </div>
{ev.type === 'CONTRACT_EXPIRY' && ev.employeeName ? ( {ev.type === 'CONTRACT_EXPIRY' && ev.employeeName ? (
<Link to={`/roster?employee=${encodeURIComponent(ev.employeeName)}`} className="text-[10px] text-primary hover:underline mt-0.5 inline-block"> <Link to={`/roster?employee=${encodeURIComponent(ev.employeeName)}`} className="text-[10px] text-primary hover:underline mt-0.5 inline-block">
+11 -5
View File
@@ -263,7 +263,12 @@ export default function ESign() {
</div> </div>
{r.remark && <div className="text-xs text-gray-400 mt-0.5">{r.remark}</div>} {r.remark && <div className="text-xs text-gray-400 mt-0.5">{r.remark}</div>}
</td> </td>
<td className="py-2 px-3">{r.employee?.name || '—'}</td> <td className="py-2 px-3">
<div className="flex flex-col gap-0.5">
<span>{r.employee?.name || '—'}</span>
{r.employee?.idCardNumber && <span className="text-gray-400 text-[11px] font-mono">{r.employee.idCardNumber}</span>}
</div>
</td>
<td className="py-2 px-3 text-gray-500">{r.employee?.department || '—'}</td> <td className="py-2 px-3 text-gray-500">{r.employee?.department || '—'}</td>
<td className="py-2 px-3"> <td className="py-2 px-3">
<span className={`px-1.5 py-0.5 rounded text-xs ${(SIGN_METHOD_CONFIG[r.signMethod] || SIGN_METHOD_CONFIG.ELECTRONIC).color}`}> <span className={`px-1.5 py-0.5 rounded text-xs ${(SIGN_METHOD_CONFIG[r.signMethod] || SIGN_METHOD_CONFIG.ELECTRONIC).color}`}>
@@ -334,7 +339,7 @@ export default function ESign() {
> >
<option value=""></option> <option value=""></option>
{rosterData.map((emp: any) => ( {rosterData.map((emp: any) => (
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}</option> <option key={emp.id} value={emp.id}>{emp.name}{emp.idCardNumber ? `${emp.idCardNumber}` : ''} - {emp.department}</option>
))} ))}
</select> </select>
</div> </div>
@@ -496,7 +501,7 @@ function PaperSignModal({ onClose, onSuccess, initialEmployeeId, initialScene, i
> >
<option value=""></option> <option value=""></option>
{rosterData.map((emp: any) => ( {rosterData.map((emp: any) => (
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}</option> <option key={emp.id} value={emp.id}>{emp.name}{emp.idCardNumber ? `${emp.idCardNumber}` : ''} - {emp.department}</option>
))} ))}
</select> </select>
</div> </div>
@@ -654,7 +659,7 @@ function ESignDetail({ id, onBack }: { id: string; onBack: () => void }) {
{(SIGN_METHOD_CONFIG[detail.signMethod] || SIGN_METHOD_CONFIG.ELECTRONIC).label} {(SIGN_METHOD_CONFIG[detail.signMethod] || SIGN_METHOD_CONFIG.ELECTRONIC).label}
</span> </span>
</div> </div>
<div className="text-xs text-gray-400 mt-0.5">{detail.employee?.name} · {detail.employee?.department}</div> <div className="text-xs text-gray-400 mt-0.5">{detail.employee?.name} · {detail.employee?.department}{detail.employee?.idCardNumber && <span className="ml-1 font-mono">{detail.employee.idCardNumber}</span>}</div>
</div> </div>
<span className={`inline-flex items-center gap-1 px-2.5 py-1 rounded text-xs ${statusCfg.color}`}> <span className={`inline-flex items-center gap-1 px-2.5 py-1 rounded text-xs ${statusCfg.color}`}>
{statusCfg.icon}{statusCfg.label} {statusCfg.icon}{statusCfg.label}
@@ -868,6 +873,7 @@ function PendingTab({ pendingList, loading, onRemind, remindLoading, remindData,
<User className="w-3.5 h-3.5 text-primary" /> <User className="w-3.5 h-3.5 text-primary" />
</div> </div>
<span className="font-medium">{emp.name}</span> <span className="font-medium">{emp.name}</span>
{emp.idCardNumber && <span className="text-xs text-gray-400 font-mono ml-1">{emp.idCardNumber}</span>}
</div> </div>
</td> </td>
<td className="py-2 px-3 text-gray-500">{emp.department || '—'}</td> <td className="py-2 px-3 text-gray-500">{emp.department || '—'}</td>
@@ -993,7 +999,7 @@ function PendingTab({ pendingList, loading, onRemind, remindLoading, remindData,
<QRCodeSVG value={remindData.url} size={200} level="M" /> <QRCodeSVG value={remindData.url} size={200} level="M" />
</div> </div>
<p className="mt-3 text-xs text-gray-600 text-center"> <p className="mt-3 text-xs text-gray-600 text-center">
<span className="font-medium">{remindData.employeeName}</span> <span className="font-medium">{remindData.employeeName}</span>{remindData.employeeIdCardNumber && <span className="font-mono text-gray-500 ml-1">{remindData.employeeIdCardNumber}</span>}
</p> </p>
<p className="mt-1 text-xs text-gray-400"> 24 </p> <p className="mt-1 text-xs text-gray-400"> 24 </p>
<div className="mt-3 w-full"> <div className="mt-3 w-full">
+2 -2
View File
@@ -1378,7 +1378,7 @@ export default function Roster() {
{previewData.results.map((r: any) => ( {previewData.results.map((r: any) => (
<div key={r.contractId} className={`p-2 rounded text-xs ${r.warning ? 'bg-amber-50 border border-amber-200' : 'bg-gray-50'}`}> <div key={r.contractId} className={`p-2 rounded text-xs ${r.warning ? 'bg-amber-50 border border-amber-200' : 'bg-gray-50'}`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-medium">{r.employeeName}</span> <span className="font-medium">{r.employeeName}{r.employeeIdCardNumber ? <span className="ml-1 font-mono text-gray-400 text-[10px]">{r.employeeIdCardNumber}</span> : null}</span>
<span className="text-gray-400">{r.department}</span> <span className="text-gray-400">{r.department}</span>
</div> </div>
<div className="text-gray-500 mt-1"> <div className="text-gray-500 mt-1">
@@ -1477,7 +1477,7 @@ export default function Roster() {
{terminatePreviewData.results.map((r: any) => ( {terminatePreviewData.results.map((r: any) => (
<div key={r.employeeId} className={`p-2 rounded text-xs ${r.warnings.length > 0 ? 'bg-red-50 border border-red-200' : 'bg-gray-50'}`}> <div key={r.employeeId} className={`p-2 rounded text-xs ${r.warnings.length > 0 ? 'bg-red-50 border border-red-200' : 'bg-gray-50'}`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-medium">{r.employeeName}</span> <span className="font-medium">{r.employeeName}{r.employeeIdCardNumber ? <span className="ml-1 font-mono text-gray-400 text-[10px]">{r.employeeIdCardNumber}</span> : null}</span>
<span className="text-gray-400">{r.department}</span> <span className="text-gray-400">{r.department}</span>
</div> </div>
<div className="text-gray-500 mt-1"> <div className="text-gray-500 mt-1">
+4 -3
View File
@@ -38,7 +38,7 @@ interface SpecialStatus {
reminderDate: string | null reminderDate: string | null
alertLevel: 'red' | 'yellow' | 'green' alertLevel: 'red' | 'yellow' | 'green'
employee: { employee: {
id: string; name: string; department: string; phone: string; gender: string; status: string id: string; name: string; department: string; phone: string; gender: string; status: string; idCardNumber?: string | null
} }
} }
@@ -367,6 +367,7 @@ export default function SpecialStatus() {
{/* 员工信息 */} {/* 员工信息 */}
<div className="mb-3"> <div className="mb-3">
<Link to={`/roster?employeeId=${item.employee.id}`} className="font-medium text-gray-900 hover:text-primary hover:underline">{item.employee.name}</Link> <Link to={`/roster?employeeId=${item.employee.id}`} className="font-medium text-gray-900 hover:text-primary hover:underline">{item.employee.name}</Link>
{item.employee.idCardNumber && <span className="text-xs text-gray-400 font-mono ml-1">{item.employee.idCardNumber}</span>}
<span className="text-sm text-gray-500 ml-2">{item.employee.department}</span> <span className="text-sm text-gray-500 ml-2">{item.employee.department}</span>
{item.employee.status === 'RESIGNED' && ( {item.employee.status === 'RESIGNED' && (
<span className="ml-2 text-xs text-gray-400"></span> <span className="ml-2 text-xs text-gray-400"></span>
@@ -458,7 +459,7 @@ export default function SpecialStatus() {
}} disabled={!!editing}> }} disabled={!!editing}>
<option value=""></option> <option value=""></option>
{employees.map((emp) => ( {employees.map((emp) => (
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}</option> <option key={emp.id} value={emp.id}>{emp.name}{emp.idCardNumber ? `${emp.idCardNumber}` : ''} - {emp.department}</option>
))} ))}
</Select> </Select>
</div> </div>
@@ -620,7 +621,7 @@ export default function SpecialStatus() {
</div> </div>
</div> </div>
<p className="text-sm text-gray-600 mb-4"> <p className="text-sm text-gray-600 mb-4">
<b>{deleteTarget.employee.name}</b> <b>{TYPE_LABELS[deleteTarget.type]}</b> <b>{deleteTarget.employee.name}{deleteTarget.employee.idCardNumber ? `${deleteTarget.employee.idCardNumber}` : ''}</b> <b>{TYPE_LABELS[deleteTarget.type]}</b>
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button className="flex-1 bg-red-500 hover:bg-red-600" onClick={handleDelete}></Button> <Button className="flex-1 bg-red-500 hover:bg-red-600" onClick={handleDelete}></Button>
+1 -1
View File
@@ -323,7 +323,7 @@ export function OvertimeCalculator() {
<tbody> <tbody>
{previewData.map((d, i) => ( {previewData.map((d, i) => (
<tr key={i} className="border-b last:border-0"> <tr key={i} className="border-b last:border-0">
<td className="py-2">{d.employeeName}</td> <td className="py-2">{d.employeeName}{d.idCardNumber ? <span className="ml-1 text-xs text-gray-400 font-mono">{d.idCardNumber}</span> : ''}</td>
<td className="py-2 text-gray-500">{d.department}</td> <td className="py-2 text-gray-500">{d.department}</td>
<td className="py-2 text-right">{d.weekdayHours}</td> <td className="py-2 text-right">{d.weekdayHours}</td>
<td className="py-2 text-right">{d.weekendHours}</td> <td className="py-2 text-right">{d.weekendHours}</td>
@@ -267,7 +267,7 @@ export default function CommercialInsuranceTab() {
else setEnrollEmployeeIds(enrollEmployeeIds.filter((id) => id !== emp.id)) else setEnrollEmployeeIds(enrollEmployeeIds.filter((id) => id !== emp.id))
}} /> }} />
</td> </td>
<td className="py-2 px-3 font-medium">{emp.name}</td> <td className="py-2 px-3 font-medium">{emp.name}{emp.idCardNumber ? <span className="ml-1 text-xs text-gray-400 font-mono">{emp.idCardNumber}</span> : ''}</td>
<td className="py-2 px-3 text-gray-500">{emp.department}</td> <td className="py-2 px-3 text-gray-500">{emp.department}</td>
<td className="py-2 px-3"> <td className="py-2 px-3">
<span className="px-2 py-0.5 rounded text-xs bg-green-50 text-safe"></span> <span className="px-2 py-0.5 rounded text-xs bg-green-50 text-safe"></span>