Files
TurboHR/frontend/src/pages/dashboard/PerformanceStats.tsx
T

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>
)
}