'use client'; import { useEffect } from 'react'; import { usePathname, useRouter } from 'next/navigation'; import { Inbox, Package, FolderKanban, Tag, Users, LayoutGrid, Lightbulb, Clock, Shield, Settings, Sparkles, TriangleAlert, MessageCircleQuestionMark, ScrollText, Database, Activity } from 'lucide-react'; import { useHasPermission } from '@/components/auth/Guard'; import { useXiaobaoWarningRisks } from '@/hooks/useXiaobaoWarningRisks'; import { useWorkspaceWorkItems } from '@/hooks/useWorkspaceWorkItems'; import { useAuthStore } from '@/stores/useAuthStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useXiaobaoRiskStore } from '@/stores/useXiaobaoRiskStore'; import { useXiaobaoWarningReadStore } from '@/stores/useXiaobaoWarningReadStore'; import { getWorkspacePendingCount } from '@/lib/workspace-engine'; import { buildXiaobaoRiskInsightPendingKey } from '@/lib/xiaobao-risk-suggestion'; import { filterXiaobaoRiskWarnings, getXiaobaoWarningRiskCount, getXiaobaoWarningUnreadUpdateCount, } from '@/lib/xiaobao-warning-view'; type NavItemConfig = { label: string; path: string; icon: any; permission: string | null; badge?: 'xiaobao-risk' | 'workspace-pending'; }; const NAV_GROUPS = [ { label: '工作区', items: [ { label: 'AI 助手', path: '/wenfan-xiaobao', icon: MessageCircleQuestionMark, permission: null as string | null }, { label: '智能预警', path: '/xiaobao-warning', icon: TriangleAlert, permission: 'xiaobao.warning:view', badge: 'xiaobao-risk' as const }, { label: '与我相关', path: '/workspace', icon: Inbox, permission: null as string | null, badge: 'workspace-pending' as const }, { label: '产品', path: '/products', icon: Package, permission: 'product:view' }, { label: '项目', path: '/projects', icon: FolderKanban, permission: 'project:view' }, { label: '版本', path: '/versions', icon: Tag, permission: 'version:view' }, { label: '需求池', path: '/requirements', icon: Lightbulb, permission: 'requirement:view' }, { label: '加班记录', path: '/overtime', icon: Clock, permission: 'overtime:view' }, ], }, { label: '管理', items: [ { label: '成员', path: '/admin/members', icon: Users, permission: 'member:view' }, { label: '角色', path: '/admin/roles', icon: Shield, permission: 'role:view' }, { label: '审计', path: '/admin/audit', icon: ScrollText, permission: 'audit:view' }, { label: '一致性', path: '/admin/consistency', icon: Database, permission: 'consistency:view' }, { label: '运维', path: '/admin/ops', icon: Activity, permission: 'ops:view' }, { label: 'AI 配置', path: '/admin/ai-config', icon: Sparkles, permission: '*' }, ], }, ]; export function Sidebar() { const pathname = usePathname(); const router = useRouter(); const { workItems } = useWorkspaceWorkItems(); const workspacePendingCount = getWorkspacePendingCount(workItems); const isActive = (path: string) => pathname === path || (path !== '/' && pathname.startsWith(path)); return ( ); } function UserBlock() { const router = useRouter(); const user = useAuthStore((s) => s.user); const role = useMemberStore((s) => s.roles.find((r) => r.id === user?.roleId)); if (!user) { return (
?

未登录

); } return (
{user.name?.[0] ?? '?'}

{user.name}

{role?.name ?? '-'}

); } function NavGroup({ label, items, isActive, onNavigate, workspacePendingCount }: { label: string; items: NavItemConfig[]; isActive: (p: string) => boolean; onNavigate: (p: string) => void; workspacePendingCount: number; }) { const visibleItems = items.filter((item) => item.permission === null || /* eslint-disable-next-line react-hooks/rules-of-hooks */ true); if (visibleItems.length === 0) return null; return (

{label}

{items.map((item) => ( ))}
); } function NavItem({ item, active, onNavigate, workspacePendingCount }: { item: NavItemConfig; active: boolean; onNavigate: (p: string) => void; workspacePendingCount: number; }) { const hasPerm = useHasPermission(item.permission ?? ''); if (item.permission && !hasPerm) return null; const Icon = item.icon; return ( ); } function XiaobaoRiskNavBadge() { const { risks } = useXiaobaoWarningRisks(); const user = useAuthStore((s) => s.user); const pendingInsightKeys = useXiaobaoRiskStore((s) => s.pendingInsightKeys); const { readStates, readStateLoaded, fetchReadStates } = useXiaobaoWarningReadStore(); useEffect(() => { if (user?.id) fetchReadStates(); }, [fetchReadStates, user?.id]); const count = getXiaobaoWarningRiskCount(risks); if (count <= 0) return null; const warningRisks = filterXiaobaoRiskWarnings(risks).map((risk) => ( pendingInsightKeys.includes(buildXiaobaoRiskInsightPendingKey(risk)) ? { ...risk, aiInsightUpdating: true } : risk )); const unreadUpdateCount = readStateLoaded ? getXiaobaoWarningUnreadUpdateCount(warningRisks, readStates, user?.id) : 0; return 0 ? 'update' : 'risk'} />; } function NavCountBadge({ count, tone = 'risk' }: { count: number; tone?: 'risk' | 'update' }) { if (count <= 0) return null; const toneClass = tone === 'update' ? 'bg-blue-600' : 'bg-red-600'; return ( {count > 99 ? '99+' : count} ); }