WuYongJie e4dcb6260a
Some checks failed
CI Check / check (push) Has been cancelled
feat: Admin页面重构 & UI三层架构系统
- 创建统一UI三层架构系统(基础层、颜色层、组合层)
- 拆分Admin页面为独立Tab组件(MonitorTab、UsersTab、DepartmentsTab等)
- 提取公共逻辑到Hooks(useToast、useDataFetch、useUserOperations等)
- 提取工具函数(validators、formatters、api等)
- 创建统一弹窗组件(Modal)和useModal Hook
- 添加错误边界和统一状态组件
- 完善类型定义和可访问性支持
- 更新文档(优化记录、优化计划、更新日志)
2025-11-22 15:17:10 +08:00

85 lines
2.9 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.

'use client'
import { memo, useCallback } from 'react'
import type { User, UserTreeNode } from '../types'
import { cardStyles, cn } from '@/lib/ui-styles'
interface UserTreeProps {
nodes: UserTreeNode[]
collapsedNodeIds: Set<string>
searchTerm: string
onToggleUserNode: (id: string) => void
renderStatusBadges: (user: User) => JSX.Element
renderActionButtons: (user: User) => JSX.Element
}
function UserTreeComponent({
nodes,
collapsedNodeIds,
searchTerm,
onToggleUserNode,
renderStatusBadges,
renderActionButtons,
}: UserTreeProps) {
const renderNodes = useCallback(
(tree: UserTreeNode[], depth = 0): JSX.Element[] =>
tree.map((node) => {
const { user } = node
const hasChildren = node.children.length > 0
const collapsed = searchTerm.trim() ? false : collapsedNodeIds.has(user.id)
return (
<div key={user.id} className="first:border-t-0">
<div
className={cn(cardStyles.hover, 'flex items-start gap-3 group')}
style={{ paddingLeft: `${depth * 24 + 16}px` }}
>
{hasChildren ? (
<button
type="button"
onClick={() => onToggleUserNode(user.id)}
className="flex-shrink-0 w-6 h-6 flex items-center justify-center rounded hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors"
title={collapsed ? '展开' : '折叠'}
>
<svg
className={cn('w-4 h-4 transform transition-transform', !collapsed && 'rotate-90')}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
) : (
<span className="flex-shrink-0 w-6 h-6" />
)}
<div className="flex-1 space-y-1">
<div className="flex flex-wrap items-center gap-2">
<span className="font-semibold text-slate-900 dark:text-white">{user.username}</span>
{renderStatusBadges(user)}
</div>
<div className="text-xs text-slate-500 dark:text-slate-400">素材:{user._count.materials}</div>
</div>
{renderActionButtons(user)}
</div>
{hasChildren && !collapsed && <div>{renderNodes(node.children, depth + 1)}</div>}
</div>
)
}),
[collapsedNodeIds, onToggleUserNode, renderActionButtons, renderStatusBadges, searchTerm],
)
if (!nodes.length) {
return <div className="text-center py-12 text-slate-500 dark:text-slate-400">暂无用户</div>
}
return <div className="divide-y divide-slate-200 dark:divide-slate-800">{renderNodes(nodes)}</div>
}
export const UserTree = memo(UserTreeComponent)
export default UserTree