Some checks failed
CI Check / check (push) Has been cancelled
- 创建统一UI三层架构系统(基础层、颜色层、组合层) - 拆分Admin页面为独立Tab组件(MonitorTab、UsersTab、DepartmentsTab等) - 提取公共逻辑到Hooks(useToast、useDataFetch、useUserOperations等) - 提取工具函数(validators、formatters、api等) - 创建统一弹窗组件(Modal)和useModal Hook - 添加错误边界和统一状态组件 - 完善类型定义和可访问性支持 - 更新文档(优化记录、优化计划、更新日志)
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
'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
|
||
|