feat: 系统优化Phase2 - 面包屑导航/侧边栏间距/制度公示阅读签收/模板变量中文化/通知类型补全
- 面包屑导航组件,集成至TopNav header - 侧边栏菜单分组间距增大,分组间分隔线 - 制度公示员工阅读签收:PolicyReadRecord模型、portal路由、管理端阅读统计 - 修复Policies.tsx民主程序推进bug(字段名/API路径/参数) - 用工文本模板变量名英文转中文显示 - 通知类型TYPE_LABELS补全(RISK_ALERT/SOCIAL_INS/OVERTIME_ALERT/PAYSLIP_READY) - 通知示例数据补充 - h2标题统一为text-sm font-medium - 新增run.md
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { useState, useRef } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import api from "../../lib/api"
|
||||
import Card from "../../components/ui/Card"
|
||||
import Button from "../../components/ui/Button"
|
||||
import { Input, Label, Select } from "../../components/ui/Input"
|
||||
import Modal from "../../components/ui/Modal"
|
||||
import { Users, FileText, AlertTriangle, Calendar, GraduationCap, TrendingUp, Scale, X, Plus, Paperclip, Trash2, Printer, Calculator, Shield, Info, Check, Eye, Download, UserX, UserPlus, Briefcase, FileSignature, DollarSign, Building2, RotateCcw, History } from "lucide-react"
|
||||
import { fmt } from "./shared"
|
||||
|
||||
// ========== 绩效记录管理 ==========
|
||||
|
||||
export default function PerformanceInfo({ employeeId, records }: { employeeId: string; records: any[] }) {
|
||||
const queryClient = useQueryClient()
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
const [form, setForm] = useState({ period: '', score: 80, grade: 'B', result: 'QUALIFIED', summary: '', improvementPlan: '', employeeAck: false, ackDate: '', reviewer: '' })
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => api.post(`/roster/${employeeId}/performance`, data),
|
||||
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
|
||||
})
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: string) => api.delete(`/roster/${employeeId}/performance/${id}`),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }),
|
||||
})
|
||||
|
||||
const resultMap: Record<string, string> = { EXCELLENT: '优秀', QUALIFIED: '合格', NEED_IMPROVE: '需改进', UNQUALIFIED: '不胜任' }
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xs font-medium">绩效考核记录({records?.length || 0}条)</h2>
|
||||
<Button size="sm" onClick={() => setShowForm(!showForm)}>新增绩效记录</Button>
|
||||
</div>
|
||||
|
||||
{showForm && (
|
||||
<Card>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div><Label>考核周期</Label><Input value={form.period} onChange={(e) => setForm({ ...form, period: e.target.value })} placeholder="如 2026-07 或 2026-Q3" /></div>
|
||||
<div><Label>考核得分</Label><Input type="number" value={form.score} onChange={(e) => setForm({ ...form, score: Number(e.target.value) })} /></div>
|
||||
<div><Label>等级</Label>
|
||||
<Select value={form.grade} onChange={(e) => setForm({ ...form, grade: e.target.value })}>
|
||||
<option value="A">A</option><option value="B">B</option><option value="C">C</option><option value="D">D</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div><Label>考核结果</Label>
|
||||
<Select value={form.result} onChange={(e) => setForm({ ...form, result: e.target.value })}>
|
||||
{Object.entries(resultMap).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="md:col-span-2"><Label>考核评语</Label><Input value={form.summary} onChange={(e) => setForm({ ...form, summary: e.target.value })} /></div>
|
||||
<div className="md:col-span-2"><Label>改进计划(不胜任时填写)</Label><Input value={form.improvementPlan} onChange={(e) => setForm({ ...form, improvementPlan: e.target.value })} placeholder="如:调岗至XX岗位,培训XX技能" /></div>
|
||||
<div><Label>考核人</Label><Input value={form.reviewer} onChange={(e) => setForm({ ...form, reviewer: e.target.value })} /></div>
|
||||
<div className="flex items-center gap-2 pt-6">
|
||||
<input type="checkbox" id="perfAck" checked={form.employeeAck} onChange={(e) => setForm({ ...form, employeeAck: e.target.checked })} />
|
||||
<label htmlFor="perfAck" className="text-xs">员工已签字确认</label>
|
||||
</div>
|
||||
{form.employeeAck && <div><Label>确认日期</Label><Input type="date" value={form.ackDate} onChange={(e) => setForm({ ...form, ackDate: e.target.value })} /></div>}
|
||||
<div className="md:col-span-2 flex gap-2"><Button onClick={() => createMutation.mutate(form)} disabled={createMutation.isPending || !form.period}>{createMutation.isPending ? '保存中...' : '保存'}</Button><Button variant="secondary" onClick={() => setShowForm(false)}>取消</Button></div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{records?.length === 0 ? (
|
||||
<Card><div className="text-center py-8 text-gray-400">暂无绩效记录</div></Card>
|
||||
) : records?.map((r) => (
|
||||
<Card key={r.id} className="p-4">
|
||||
<div className="flex justify-between items-start gap-3">
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs font-medium text-gray-700">{r.period}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs ${r.result === 'EXCELLENT' ? 'bg-green-50 text-safe' : r.result === 'QUALIFIED' ? 'bg-blue-50 text-blue-600' : r.result === 'NEED_IMPROVE' ? 'bg-amber-50 text-amber-600' : 'bg-red-50 text-danger'}`}>
|
||||
{resultMap[r.result] || r.result}
|
||||
</span>
|
||||
<span className="px-2 py-0.5 rounded bg-gray-100 text-gray-600 text-xs">得分 {r.score} · 等级 {r.grade}</span>
|
||||
</div>
|
||||
{r.summary && <div className="text-xs text-gray-600 leading-relaxed">{r.summary}</div>}
|
||||
{r.improvementPlan && (
|
||||
<div className="text-xs bg-amber-50 text-amber-700 px-2 py-1.5 rounded leading-relaxed">
|
||||
<span className="font-medium">改进计划</span>:{r.improvementPlan}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-4 text-xs pt-1 border-t text-gray-400">
|
||||
{r.employeeAck ? (
|
||||
<span className="text-safe flex items-center gap-1"><Check className="w-3 h-3" />已签字({r.ackDate?.toString().slice(0, 10)})</span>
|
||||
) : (
|
||||
<span className="text-warning flex items-center gap-1"><AlertTriangle className="w-3 h-3" />未签字</span>
|
||||
)}
|
||||
{r.reviewer && <span>考核人:{r.reviewer}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => deleteMutation.mutate(r.id)} className="text-xs text-gray-300 hover:text-danger shrink-0">删除</button>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user