Files
TurboHR/frontend/src/pages/dashboard/TaskCenter.tsx
T
freedakgmail 6ec939dc7f feat: 发薪日期多选+提前N天提醒+电子签设置区域修复
- Schema: 去掉 payrollFrequency,新增 payrollDays (JSON数组) + payrollReminderDays (Int)
- 设置页: 发薪日期改为1-28号多选按钮,新增提前提醒天数设置
- 设置页: 恢复电子签署设置区域(3个开关:规章制度/工资条/入职文件)
- 工作日历: 发薪日期作为 PAYROLL_DAY 事件显示
- 工作台: 提前N天提醒发薪日期,N可配置
- TaskCenter: 新增发薪提醒分类图标
- seed文件: 更新为 payrollDays 格式
2026-08-05 08:04:39 +08:00

140 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* TaskCenter 任务中心组件 — 展示待办队列、人员动态和下一步行动
* 从 /dashboard/workspace/next-actions 获取数据,按优先级分组展示
*/
import { useQuery } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { AlertCircle, Layers, FileText, Heart, ArrowRight, ListTodo, Wallet } from 'lucide-react'
import { dashboardApi } from '../../lib/api-services'
import { InlineAlert } from '../../components/ui/InlineAlert'
interface NextAction {
id: string
title: string
subtitle?: string
type?: string
level?: string
dueDate?: string
link: string
}
interface ActionGroup {
category: string
priority: 'high' | 'medium' | 'low'
items: NextAction[]
}
const categoryIcons: Record<string, typeof AlertCircle> = {
'待办事项': AlertCircle,
'发薪批次': Layers,
'发薪提醒': Wallet,
'合同到期': FileText,
'特殊状态': Heart,
}
const priorityColors: Record<string, string> = {
high: 'text-danger',
medium: 'text-warning',
low: 'text-info',
}
/**
* 任务中心 — 首页核心组件,聚合展示需要关注的行动项
*/
export function TaskCenter() {
const { data, isLoading } = useQuery<{ actions: ActionGroup[]; totalCount: number }>({
queryKey: ['next-actions'],
queryFn: async () => {
return await dashboardApi.nextActions()
},
staleTime: 60_000,
})
if (isLoading) {
return (
<div className="card p-4">
<div className="flex items-center gap-2 mb-3">
<ListTodo className="w-4 h-4 text-brand-600" />
<h3 className="text-sm font-medium text-ink-700"></h3>
</div>
<div className="space-y-2">
{[1, 2, 3].map(i => (
<div key={i} className="h-12 rounded-md bg-surface-muted animate-pulse" />
))}
</div>
</div>
)
}
if (!data || data.totalCount === 0) {
return (
<div className="card p-4">
<div className="flex items-center gap-2 mb-3">
<ListTodo className="w-4 h-4 text-brand-600" />
<h3 className="text-sm font-medium text-ink-700"></h3>
</div>
<InlineAlert type="success" title="暂无待办">
</InlineAlert>
</div>
)
}
return (
<div className="card p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<ListTodo className="w-4 h-4 text-brand-600" />
<h3 className="text-sm font-medium text-ink-700"></h3>
<span className="text-xs text-ink-400">{data.totalCount} </span>
</div>
</div>
<div className="space-y-3">
{data.actions.map((group) => {
const Icon = categoryIcons[group.category] || AlertCircle
return (
<div key={group.category}>
{/* 分类标题 */}
<div className="flex items-center gap-1.5 mb-1.5">
<Icon className={`w-3.5 h-3.5 ${priorityColors[group.priority]}`} />
<span className="text-xs font-medium text-ink-600">{group.category}</span>
<span className="text-xs text-ink-400">({group.items.length})</span>
</div>
{/* 行动项列表 */}
<div className="space-y-1 ml-5 max-h-60 overflow-y-auto">
{group.items.map((item) => (
<Link
key={item.id}
to={item.link}
className="flex items-center justify-between gap-2 px-2 py-1.5 rounded-md hover:bg-surface-muted transition-colors group"
>
<div className="flex-1 min-w-0">
<div className="text-sm text-ink-900 truncate">{item.title}</div>
{item.subtitle && (
<div className="text-xs text-ink-400 truncate">{item.subtitle}</div>
)}
</div>
{item.dueDate && (
<span className="text-xs text-ink-400 shrink-0">{item.dueDate}</span>
)}
<ArrowRight className="w-3.5 h-3.5 text-ink-300 group-hover:text-brand-600 shrink-0 transition-colors" />
</Link>
))}
{group.items.length > 5 && (
<Link
to={group.category === '发薪批次' ? '/money' : group.category === '合同到期' ? '/roster' : group.category === '待办事项' ? '/risk-center' : '/'}
className="block text-xs text-brand-600 hover:underline px-2 py-1"
>
{group.items.length}
</Link>
)}
</div>
</div>
)
})}
</div>
</div>
)
}