From 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Tue, 25 Mar 2025 15:55:45 +0900
Subject: initial commit

---
 components/system/permissionsTree.tsx | 167 ++++++++++++++++++++++++++++++++++
 1 file changed, 167 insertions(+)
 create mode 100644 components/system/permissionsTree.tsx

(limited to 'components/system/permissionsTree.tsx')

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
-- 
cgit v1.2.3

