diff options
Diffstat (limited to 'components/system/permissionsTree.tsx')
| -rw-r--r-- | components/system/permissionsTree.tsx | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/components/system/permissionsTree.tsx b/components/system/permissionsTree.tsx new file mode 100644 index 00000000..8f6adfb0 --- /dev/null +++ b/components/system/permissionsTree.tsx @@ -0,0 +1,167 @@ +"use client" + +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import { styled } from '@mui/material/styles'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; +import { TreeItem, treeItemClasses } from '@mui/x-tree-view/TreeItem'; +import { Minus, MinusSquare, Plus, SquarePlus } from 'lucide-react'; +import { Button } from "@/components/ui/button"; +import { mainNav, additionalNav, MenuSection } from "@/config/menuConfig"; +import { PermissionDialog } from './permissionDialog'; + +// ------------------- Custom TreeItem Style ------------------- +const CustomTreeItem = styled(TreeItem)({ + [`& .${treeItemClasses.iconContainer}`]: { + '& .close': { + opacity: 0.3, + }, + }, +}); + +function CloseSquare(props: SvgIconProps) { + return ( + <SvgIcon + className="close" + fontSize="inherit" + style={{ width: 14, height: 14 }} + {...props} + > + {/* tslint:disable-next-line: max-line-length */} + <path d="M17.485 17.512q-.281.281-.682.281t-.696-.268l-4.12-4.147-4.12 4.147q-.294.268-.696.268t-.682-.281-.281-.682.294-.669l4.12-4.147-4.12-4.147q-.294-.268-.294-.669t.281-.682.682-.281.696.268l4.12 4.147 4.12-4.147q.294-.268.696-.268t.682.281 .281.669-.294.682l-4.12 4.147 4.12 4.147q.294.268 .294.669t-.281.682zM22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0z" /> + </SvgIcon> + ); +} + + +interface SelectedKey { + key: string; + title: string; +} + +export default function PermissionsTree() { + const [expandedItems, setExpandedItems] = React.useState<string[]>([]); + const [dialogOpen, setDialogOpen] = React.useState(false); + const [selectedKey, setSelectedKey] = React.useState<SelectedKey | null>(null); + + const handleExpandedItemsChange = ( + event: React.SyntheticEvent, + itemIds: string[], + ) => { + setExpandedItems(itemIds); + }; + + const handleExpandClick = () => { + if (expandedItems.length === 0) { + // 모든 노드를 펼치기 + // 실제로는 mainNav와 additionalNav를 순회해 itemId를 전부 수집하는 방식 + setExpandedItems([...collectAllIds()]); + } else { + setExpandedItems([]); + } + }; + + // (4) 수동으로 "모든 TreeItem의 itemId"를 수집하는 함수 + const collectAllIds = React.useCallback(() => { + const ids: string[] = []; + + // mainNav: 상위 = section.title, 하위 = item.title + mainNav.forEach((section) => { + ids.push(section.title); // 상위 + section.items.forEach((itm) => ids.push(itm.title)); + }); + + // additionalNav를 "기타메뉴" 아래에 넣을 경우, "기타메뉴" 라는 itemId + each item + additionalNav.forEach((itm) => ids.push(itm.title)); + return ids; + }, []); + + + function handleItemClick(key: SelectedKey) { + // 1) Dialog 열기 + setSelectedKey(key); // 이 값은 Dialog에서 어떤 메뉴인지 식별에 사용 + setDialogOpen(true); + } + + // (5) 실제 렌더 + return ( + <div className='lg:max-w-2xl'> + <Stack spacing={2}> + <div> + <Button onClick={handleExpandClick} type='button'> + {expandedItems.length === 0 ? ( + <> + <Plus /> + Expand All + </> + ) : ( + <> + <Minus /> + Collapse All + </> + )} + </Button> + </div> + + <Box sx={{ minHeight: 352, minWidth: 250 }}> + <SimpleTreeView + // 아래 props로 아이콘 지정 + slots={{ + expandIcon: SquarePlus, + collapseIcon: MinusSquare, + endIcon: CloseSquare, + }} + expansionTrigger="iconContainer" + onExpandedItemsChange={handleExpandedItemsChange} + expandedItems={expandedItems} + > + {/* (A) mainNav를 트리로 렌더 */} + {mainNav.map((section) => ( + <CustomTreeItem + key={section.title} + itemId={section.title} + label={section.title} + > + {section.items.map((itm) => { + const lastSegment = itm.href.split("/").pop() || itm.title; + const key = { key: lastSegment, title: itm.title } + return ( + <CustomTreeItem + key={lastSegment} + itemId={lastSegment} + label={itm.title} + onClick={() => handleItemClick(key)} + /> + ); + })} + </CustomTreeItem> + ))} + + + {additionalNav.map((itm) => { + const lastSegment = itm.href.split("/").pop() || itm.title; + const key = { key: lastSegment, title: itm.title } + return ( + <CustomTreeItem + key={lastSegment} + itemId={lastSegment} + label={itm.title} + onClick={() => handleItemClick(key)} + /> + ); + })} + </SimpleTreeView> + </Box> + </Stack> + + <PermissionDialog + open={dialogOpen} + onOpenChange={setDialogOpen} + itemKey={selectedKey?.key} + itemTitle={selectedKey?.title} + /> + </div> + ); +}
\ No newline at end of file |
