Files
TurboHR/frontend/src/components/OnboardingGuide.tsx
T

149 lines
4.6 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 { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { X, ArrowRight, Home, Users, Calculator, CalendarCheck, ShieldAlert, Bot } from 'lucide-react'
const STORAGE_KEY = 'hr-onboarding-dismissed'
const modules = [
{
icon: Home,
color: 'text-blue-600',
bg: 'bg-blue-50',
title: '工作台',
desc: '风险总览、待办事项、日历事件',
path: '/',
},
{
icon: Users,
color: 'text-indigo-600',
bg: 'bg-indigo-50',
title: '团队管理',
desc: '花名册、用工办理、离职管理、特殊状态',
path: '/roster',
},
{
icon: Calculator,
color: 'text-amber-600',
bg: 'bg-amber-50',
title: '薪酬管理',
desc: '发薪批次、工资条、社保公积金、薪酬分析',
path: '/money',
},
{
icon: CalendarCheck,
color: 'text-green-600',
bg: 'bg-green-50',
title: '考勤时间',
desc: '考勤打卡、排班管理、休假审批',
path: '/attendance',
},
{
icon: ShieldAlert,
color: 'text-red-600',
bg: 'bg-red-50',
title: '合规风控',
desc: '风险中心、证据链、规章制度、用工体检',
path: '/risk-center',
},
{
icon: Bot,
color: 'text-purple-600',
bg: 'bg-purple-50',
title: 'AI 助手',
desc: '智能咨询、合同审查、判赔预测、人力分析',
path: '/ai-assistant',
},
]
export function isOnboardingDismissed() {
return localStorage.getItem(STORAGE_KEY) === '1'
}
export function dismissOnboarding() {
localStorage.setItem(STORAGE_KEY, '1')
}
export function resetOnboarding() {
localStorage.removeItem(STORAGE_KEY)
}
export default function OnboardingGuide() {
const [visible, setVisible] = useState(false)
const [dontShow, setDontShow] = useState(false)
const navigate = useNavigate()
useEffect(() => {
if (!isOnboardingDismissed()) {
setVisible(true)
}
}, [])
const close = () => {
if (dontShow) dismissOnboarding()
setVisible(false)
}
const goTo = (path: string) => {
if (dontShow) dismissOnboarding()
setVisible(false)
navigate(path)
}
if (!visible) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-xl max-w-lg w-full mx-4 overflow-hidden">
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-100">
<h2 className="text-base font-semibold">使</h2>
<button onClick={close} className="text-gray-400 hover:text-gray-600">
<X className="w-5 h-5" />
</button>
</div>
<div className="px-5 py-4">
<p className="text-sm text-gray-500 mb-4"> 6 </p>
<div className="grid grid-cols-2 gap-3">
{modules.map((m) => {
const Icon = m.icon
return (
<button
key={m.title}
onClick={() => goTo(m.path)}
className="flex items-start gap-3 p-3 rounded-lg border border-gray-100 hover:border-primary/30 hover:bg-primary/[0.02] transition-all text-left"
>
<div className={`flex items-center justify-center w-9 h-9 rounded-lg ${m.bg} ${m.color} shrink-0`}>
<Icon className="w-4.5 h-4.5" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-gray-800">{m.title}</div>
<div className="text-xs text-gray-500 mt-0.5 leading-relaxed">{m.desc}</div>
</div>
</button>
)
})}
</div>
<div className="mt-4 flex items-center justify-between">
<label className="flex items-center gap-2 text-sm text-gray-500 cursor-pointer">
<input
type="checkbox"
checked={dontShow}
onChange={(e) => setDontShow(e.target.checked)}
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary/10"
/>
</label>
<button
onClick={close}
className="flex items-center gap-1 px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-primary/90 transition-colors"
>
使
<ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
)
}