75a483bfdb
- 新增人员预测tab,基于多维度产能指标生成招聘/优化建议 - 规则引擎:7条优化规则 + 7条招聘规则 + 4步决策逻辑 - 动态标准值:基于全部门店中位数计算,替代硬编码阈值 - 互斥逻辑:有优化信号时抑制所有招聘信号 - 占比超配时不触发招聘信号(R9/R11/R12/R13) - 离职缺口需出勤不足才触发招聘(R10) - 管理岗满编时不触发出勤不足招聘(R9) - 前端可折叠规则引擎面板,展示全部规则和触发条件 - 产能指标对比展示实际值 vs 中位数标准
115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
||
import api from '@/lib/api'
|
||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||
|
||
const LEVEL_STYLES: Record<string, { bg: string; border: string; text: string; icon: string }> = {
|
||
red: { bg: 'bg-red-50', border: 'border-red-200', text: 'text-red-700', icon: '🔴' },
|
||
orange: { bg: 'bg-orange-50', border: 'border-orange-200', text: 'text-orange-700', icon: '🟠' },
|
||
yellow: { bg: 'bg-yellow-50', border: 'border-yellow-200', text: 'text-yellow-700', icon: '🟡' },
|
||
green: { bg: 'bg-green-50', border: 'border-green-200', text: 'text-green-700', icon: '🟢' },
|
||
}
|
||
|
||
const CATEGORY_LABELS: Record<string, string> = {
|
||
'人效': '人效分析',
|
||
'成本': '成本管控',
|
||
'加班': '加班管理',
|
||
'客流': '客流规律',
|
||
'考勤': '考勤管理',
|
||
'离职': '人员稳定',
|
||
}
|
||
|
||
export function OverallAnalysisTab() {
|
||
const { data, isLoading } = useQuery({
|
||
queryKey: ['ss/overall-analysis'],
|
||
queryFn: () => api.get('/smart-scheduling/overall-analysis'),
|
||
})
|
||
|
||
if (isLoading) return <LoadingSpinner text="生成总体智能分析..." />
|
||
|
||
const result = (data as any)?.data || {}
|
||
const kpis: any[] = result.kpis || []
|
||
const insights: any[] = result.insights || []
|
||
|
||
const redCount = insights.filter(i => i.level === 'red').length
|
||
const orangeCount = insights.filter(i => i.level === 'orange').length
|
||
const yellowCount = insights.filter(i => i.level === 'yellow').length
|
||
const greenCount = insights.filter(i => i.level === 'green').length
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* KPI 卡片 */}
|
||
<div className="grid grid-cols-5 gap-3">
|
||
{kpis.map((k) => (
|
||
<div key={k.label} className="rounded-lg border p-3 text-center">
|
||
<p className="text-xs text-muted-foreground">{k.label}</p>
|
||
<p className="text-xl font-bold mt-1">
|
||
{k.value}
|
||
<span className="text-xs font-normal text-muted-foreground ml-1">{k.unit}</span>
|
||
</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 预警汇总 */}
|
||
<div className="grid grid-cols-4 gap-3">
|
||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||
<p className="text-xs text-red-600">红色预警</p>
|
||
<p className="text-2xl font-bold text-red-700">{redCount}</p>
|
||
</div>
|
||
<div className="rounded-lg border border-orange-200 bg-orange-50 p-3">
|
||
<p className="text-xs text-orange-600">橙色预警</p>
|
||
<p className="text-2xl font-bold text-orange-700">{orangeCount}</p>
|
||
</div>
|
||
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-3">
|
||
<p className="text-xs text-yellow-600">黄色提醒</p>
|
||
<p className="text-2xl font-bold text-yellow-700">{yellowCount}</p>
|
||
</div>
|
||
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
|
||
<p className="text-xs text-green-600">良好表现</p>
|
||
<p className="text-2xl font-bold text-green-700">{greenCount}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 智能诊断建议 */}
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-lg font-bold">智能诊断建议</h2>
|
||
<span className="text-xs text-muted-foreground">基于人效、客流、考勤、离职等多维度数据自动生成</span>
|
||
</div>
|
||
|
||
{insights.length === 0 ? (
|
||
<div className="rounded-lg border p-8 text-center text-muted-foreground">
|
||
暂无异常,各项指标正常
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{insights.map((ins, i) => {
|
||
const style = LEVEL_STYLES[ins.level] || LEVEL_STYLES.yellow
|
||
return (
|
||
<div key={i} className={`rounded-lg border ${style.border} ${style.bg} p-4`}>
|
||
<div className="flex items-start gap-3">
|
||
<span className="text-lg mt-0.5">{style.icon}</span>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<span className={`text-xs px-2 py-0.5 rounded bg-white/60 ${style.text} font-medium`}>
|
||
{CATEGORY_LABELS[ins.category] || ins.category}
|
||
</span>
|
||
<h3 className="font-bold text-sm">{ins.title}</h3>
|
||
</div>
|
||
<p className="text-sm text-muted-foreground mb-2">{ins.detail}</p>
|
||
<div className="flex items-start gap-2">
|
||
<span className="text-xs text-muted-foreground whitespace-nowrap">💡 建议:</span>
|
||
<p className="text-sm">{ins.suggestion}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|