Files
SBrainCO/client/src/components/smart-scheduling/OverallAnalysisTab.tsx
T
freedakgmail 75a483bfdb feat: 智能排班人员预测系统
- 新增人员预测tab,基于多维度产能指标生成招聘/优化建议
- 规则引擎:7条优化规则 + 7条招聘规则 + 4步决策逻辑
- 动态标准值:基于全部门店中位数计算,替代硬编码阈值
- 互斥逻辑:有优化信号时抑制所有招聘信号
- 占比超配时不触发招聘信号(R9/R11/R12/R13)
- 离职缺口需出勤不足才触发招聘(R10)
- 管理岗满编时不触发出勤不足招聘(R9)
- 前端可折叠规则引擎面板,展示全部规则和触发条件
- 产能指标对比展示实际值 vs 中位数标准
2026-07-29 23:41:56 +08:00

115 lines
4.9 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.
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>
)
}