feat: Sprint 1 — 设计Token系统 + 新导航5分组 + AppShell/PageHeader/FilterBar/DataTable组件 + 花名册导出扩充19字段 + 社保/公积金不缴纳选项 + 个税申报表导出 + 入离职/绩效统计看板
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 绩效统计看板 — 展示绩效等级分布和部门/周期对比
|
||||
*/
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user