'use client'; import { useMemo, useState } from 'react'; import { FoxListContainer, type FoxListColumn, } from '@fox/core/components/fox-list-container'; import { FoxStatusIndicator } from '@fox/core/components/fox-status-indicator'; import { FoxCaretDownIcon, FoxCaretRightIcon, FoxFileIcon, FoxFolderIcon, } from '@fox/core/icons'; import { buildMenuTree, collectNodeIds, collectRoleOptions, flattenOpenNodes, formatActiveLabel, formatOptionalValue, type SystemMenu, type SystemMenuNode, } from '@/lib/domain/system-menu'; import { MenuFormModal } from './menu-form-modal'; import { MenuRowActions } from './menu-row-actions'; import styles from './menu-tree.module.scss'; interface MenuTreeListProps { items: SystemMenu[]; } type FormTarget = | { mode: 'edit'; node: SystemMenuNode } | { mode: 'create'; parent: SystemMenuNode }; /** 깊이만큼 이름을 밀어 넣는다. 표 칸이라 여백은 인라인으로 준다. */ const INDENT_REM = 2; export function MenuTreeList({ items }: MenuTreeListProps) { const tree = useMemo(() => buildMenuTree(items), [items]); const roleOptions = useMemo(() => collectRoleOptions(items), [items]); const byId = useMemo(() => new Map(items.map((item) => [item.id, item])), [items]); const [closedIds, setClosedIds] = useState>(new Set()); const [target, setTarget] = useState(null); const isOpen = (id: string) => !closedIds.has(id); function toggle(id: string) { setClosedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); } const allBranchIds = useMemo(() => collectNodeIds(tree), [tree]); const isAllOpen = closedIds.size === 0; function toggleAll() { setClosedIds(isAllOpen ? new Set(allBranchIds) : new Set()); } const rows = flattenOpenNodes(tree, isOpen); const columns: FoxListColumn[] = [ { key: 'id', header: '번호', width: 100 }, { key: 'toggle', header: ( ), width: 60, render: (row) => row.children.length > 0 ? ( ) : null, }, { key: 'name', header: '메뉴명', width: 420, emphasis: true, render: (row) => ( {row.children.length > 0 ? : } {row.name} ), }, { key: 'sortOrder', header: '메뉴순번', width: 120, align: 'center' }, { key: 'isActive', header: '활성여부', width: 120, render: (row) => ( ), }, { key: 'roleName', header: '권한', width: 160, render: (row) => formatOptionalValue(row.roleName), }, { key: 'actions', header: '관리', width: 140, render: (row) => ( setTarget({ mode: 'edit', node })} onAddChild={(node) => setTarget({ mode: 'create', parent: node })} /> ), }, ]; return ( <> title="메뉴관리" breadcrumb={[ { label: '홈', href: '/' }, { label: '시스템관리' }, { label: '메뉴관리' }, ]} caption="메뉴 목록" columns={columns} rows={rows} rowKey={(row) => row.id} totalCount={items.length} emptyMessage="등록된 메뉴가 없습니다." searchHidden /> {target && ( setTarget(null)} /> )} ); }