feat: 实现20260730优化方案全部功能
- AI文件审查:.docx上传提取文本,支持多种文档类型 - 用工办理工作流:WorkProcess页面+后端API,支持入职/续签/终止等流程 - 企业自建文本库:Templates页面Tab切换,企业模板CRUD+渲染+下载Word - 考勤发布:Attendance发布/取消发布按钮,员工端MyAttendance页面 - 工资条发布:Money发布/定时发送按钮+弹窗,portal端publishStatus过滤 - 合同到期弹窗:Dashboard合同到期预警可点击打开弹窗,支持续签/终止操作 - Prisma schema新增WorkProcess/EnterpriseTemplate/AttendancePublish模型 - 前后端编译验证全部通过
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Loader2, CalendarCheck } from 'lucide-react'
|
||||
import api from '../../lib/api'
|
||||
|
||||
export default function MyAttendance() {
|
||||
const [month, setMonth] = useState(new Date().toISOString().slice(0, 7))
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['portal-attendance', month],
|
||||
queryFn: async () => {
|
||||
const res = await api.get('/portal/attendance', { params: { month } }) as any
|
||||
return res.data
|
||||
},
|
||||
})
|
||||
|
||||
const records = data?.records || []
|
||||
const published = data?.published || false
|
||||
|
||||
// 生成月份列表(最近6个月)
|
||||
const months: string[] = []
|
||||
const now = new Date()
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
||||
months.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarCheck className="w-5 h-5 text-primary" />
|
||||
<h1 className="text-base font-bold">我的考勤</h1>
|
||||
</div>
|
||||
|
||||
{/* 月份选择 */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-1">
|
||||
{months.map(m => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMonth(m)}
|
||||
className={`px-3 py-1.5 rounded-md text-xs whitespace-nowrap transition-colors ${
|
||||
month === m
|
||||
? 'bg-primary text-white font-medium'
|
||||
: 'bg-white border border-gray-200 text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{m}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-gray-400" />
|
||||
</div>
|
||||
) : !published ? (
|
||||
<div className="bg-white rounded-lg p-8 text-center">
|
||||
<p className="text-sm text-gray-400">{month} 月考勤表尚未发布</p>
|
||||
</div>
|
||||
) : records.length === 0 ? (
|
||||
<div className="bg-white rounded-lg p-8 text-center">
|
||||
<p className="text-sm text-gray-400">暂无考勤记录</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-gray-100">
|
||||
<h2 className="text-sm font-medium">{data?.title || `${month} 月考勤表`}</h2>
|
||||
</div>
|
||||
<div className="divide-y divide-gray-50">
|
||||
{records.map((record: any) => (
|
||||
<div key={record.id} className="flex items-center px-4 py-2.5">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm text-gray-900">
|
||||
{new Date(record.date).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', weekday: 'short' })}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{record.checkInTime ? `上班 ${record.checkInTime}` : '未打卡'}
|
||||
{record.checkOutTime ? ` · 下班 ${record.checkOutTime}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${
|
||||
record.status === 'NORMAL' ? 'bg-green-50 text-safe' :
|
||||
record.status === 'LATE' ? 'bg-amber-50 text-amber-700' :
|
||||
record.status === 'ABSENT' ? 'bg-red-50 text-red-700' :
|
||||
record.status === 'LEAVE' ? 'bg-blue-50 text-blue-700' :
|
||||
'bg-gray-50 text-gray-600'
|
||||
}`}>
|
||||
{record.statusText || record.status || '未知'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user