123 lines
4.5 KiB
TypeScript
123 lines
4.5 KiB
TypeScript
/**
|
|
* 绩效统计看板 — 展示绩效等级分布和部门/周期对比
|
|
*/
|
|
import { useState } from 'react'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid, Legend } from 'recharts'
|
|
import { Award, TrendingUp } from 'lucide-react'
|
|
import api from '../../lib/api'
|
|
|
|
const GRADE_COLORS: Record<string, string> = {
|
|
'A': '#237A57',
|
|
'B': '#356A8A',
|
|
'C': '#A76113',
|
|
'D': '#B83232',
|
|
'S': '#C7442E',
|
|
'未评级': '#9CA39B',
|
|
}
|
|
|
|
export default function PerformanceStats() {
|
|
const [period, setPeriod] = useState(new Date().getFullYear().toString())
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['performance-stats', period],
|
|
queryFn: async () => {
|
|
const res = await api.get(`/dashboard/performance-stats?period=${period}`)
|
|
return res.data.data
|
|
},
|
|
})
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="card p-4">
|
|
<div className="animate-pulse space-y-3">
|
|
<div className="h-4 bg-surface-muted rounded w-32" />
|
|
<div className="h-48 bg-surface-muted rounded" />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!data || data.total === 0) {
|
|
return (
|
|
<div className="card p-4">
|
|
<h3 className="text-sm font-medium text-ink-700 mb-2">绩效统计</h3>
|
|
<div className="flex items-center justify-center h-32 text-ink-400 text-sm">暂无数据</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="card p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="text-sm font-medium text-ink-700">绩效统计</h3>
|
|
<select
|
|
value={period}
|
|
onChange={(e) => setPeriod(e.target.value)}
|
|
className="text-xs px-2 py-1 rounded border border-border-default bg-surface-card text-ink-700 focus:outline-none focus:ring-2 focus:ring-brand-600/30"
|
|
>
|
|
{[new Date().getFullYear(), new Date().getFullYear() - 1].map(y => (
|
|
<option key={y} value={y}>{y} 年</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* 汇总 */}
|
|
<div className="grid grid-cols-2 gap-3 mb-4">
|
|
<div className="flex flex-col">
|
|
<span className="text-xs text-ink-500">参评人数</span>
|
|
<span className="text-lg font-semibold text-ink-900 flex items-center gap-1">
|
|
<Award className="w-4 h-4" />{data.total}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-xs text-ink-500">平均分</span>
|
|
<span className="text-lg font-semibold text-info flex items-center gap-1">
|
|
<TrendingUp className="w-4 h-4" />{data.overallAvgScore}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* 等级分布饼图 */}
|
|
<div>
|
|
<p className="text-xs text-ink-500 mb-2">等级分布</p>
|
|
<ResponsiveContainer width="100%" height={160}>
|
|
<PieChart>
|
|
<Pie
|
|
data={data.gradeDistribution}
|
|
dataKey="value"
|
|
nameKey="name"
|
|
cx="50%"
|
|
cy="50%"
|
|
outerRadius={60}
|
|
label={({ name, value }: any) => `${name}: ${value}`}
|
|
labelLine={false}
|
|
>
|
|
{data.gradeDistribution.map((entry: any, i: number) => (
|
|
<Cell key={i} fill={GRADE_COLORS[entry.name] || '#9CA39B'} />
|
|
))}
|
|
</Pie>
|
|
<Tooltip contentStyle={{ fontSize: 12, borderRadius: 8, border: '1px solid #DDE1DD' }} />
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* 部门平均分柱状图 */}
|
|
<div>
|
|
<p className="text-xs text-ink-500 mb-2">部门平均分</p>
|
|
<ResponsiveContainer width="100%" height={160}>
|
|
<BarChart data={data.departmentDistribution} layout="vertical" margin={{ top: 4, right: 8, bottom: 0, left: 20 }}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#ECEEEC" horizontal={false} />
|
|
<XAxis type="number" tick={{ fontSize: 10, fill: '#7A8278' }} />
|
|
<YAxis type="category" dataKey="name" tick={{ fontSize: 10, fill: '#7A8278' }} width={60} />
|
|
<Tooltip contentStyle={{ fontSize: 12, borderRadius: 8, border: '1px solid #DDE1DD' }} />
|
|
<Bar dataKey="avgScore" name="平均分" fill="#356A8A" radius={[0, 3, 3, 0]} />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|