From 04ed774ff60a83c00711d4e8615cb4122954dba5 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 4 Dec 2025 19:46:55 +0900
Subject: (김준회) 메뉴 관리기능 초안 개발 (시딩 필요)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/layout/DynamicMenuRender.tsx | 146 ++++++++++++++++++++++++++++++++
 1 file changed, 146 insertions(+)
 create mode 100644 components/layout/DynamicMenuRender.tsx

(limited to 'components/layout/DynamicMenuRender.tsx')

diff --git a/components/layout/DynamicMenuRender.tsx b/components/layout/DynamicMenuRender.tsx
new file mode 100644
index 00000000..f94223ae
--- /dev/null
+++ b/components/layout/DynamicMenuRender.tsx
@@ -0,0 +1,146 @@
+"use client";
+
+import Link from "next/link";
+import { cn } from "@/lib/utils";
+import { NavigationMenuLink } from "@/components/ui/navigation-menu";
+import type { MenuTreeNode } from "@/lib/menu-v2/types";
+
+interface DynamicMenuRenderProps {
+  groups: MenuTreeNode[] | undefined;
+  lng: string;
+  getTitle: (node: MenuTreeNode) => string;
+  getDescription: (node: MenuTreeNode) => string | null;
+  onItemClick?: () => void;
+}
+
+export default function DynamicMenuRender({
+  groups,
+  lng,
+  getTitle,
+  getDescription,
+  onItemClick,
+}: DynamicMenuRenderProps) {
+  if (!groups || groups.length === 0) {
+    return (
+      <div className="p-4 text-sm text-muted-foreground">
+        메뉴가 없습니다.
+      </div>
+    );
+  }
+
+  // 그룹별로 메뉴 분류
+  const groupedMenus = new Map<string, MenuTreeNode[]>();
+  const ungroupedMenus: MenuTreeNode[] = [];
+
+  for (const item of groups) {
+    if (item.nodeType === "group") {
+      // 그룹인 경우, 그룹의 children을 해당 그룹에 추가
+      const groupTitle = getTitle(item);
+      if (!groupedMenus.has(groupTitle)) {
+        groupedMenus.set(groupTitle, []);
+      }
+      if (item.children) {
+        groupedMenus.get(groupTitle)!.push(...item.children);
+      }
+    } else if (item.nodeType === "menu") {
+      // 직접 메뉴인 경우 (그룹 없이 직접 메뉴그룹에 속한 경우)
+      ungroupedMenus.push(item);
+    }
+  }
+
+  // 그룹이 없고 메뉴만 있는 경우 - 단순 그리드 렌더링
+  if (groupedMenus.size === 0 && ungroupedMenus.length > 0) {
+    return (
+      <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
+        {ungroupedMenus.map((menu) => (
+          <MenuListItem
+            key={menu.id}
+            href={`/${lng}${menu.menuPath}`}
+            title={getTitle(menu)}
+            onClick={onItemClick}
+          >
+            {getDescription(menu)}
+          </MenuListItem>
+        ))}
+      </ul>
+    );
+  }
+
+  // 그룹별 렌더링 - 가로 스크롤 지원
+  // 컨텐츠가 85vw를 초과할 때만 스크롤 발생
+  return (
+    <div className="p-4 max-w-[85vw]">
+      <div className="flex gap-6 overflow-x-auto">
+        {/* 그룹화되지 않은 메뉴 (있는 경우) */}
+        {ungroupedMenus.length > 0 && (
+          <div className="w-[200px] flex-shrink-0">
+            <ul className="space-y-2">
+              {ungroupedMenus.map((menu) => (
+                <MenuListItem
+                  key={menu.id}
+                  href={`/${lng}${menu.menuPath}`}
+                  title={getTitle(menu)}
+                  onClick={onItemClick}
+                >
+                  {getDescription(menu)}
+                </MenuListItem>
+              ))}
+            </ul>
+          </div>
+        )}
+
+        {/* 그룹별 메뉴 - 순서대로 가로 배치 */}
+        {Array.from(groupedMenus.entries()).map(([groupTitle, menus]) => (
+          <div key={groupTitle} className="w-[200px] flex-shrink-0">
+            <h4 className="mb-2 text-sm font-semibold text-muted-foreground whitespace-nowrap">
+              {groupTitle}
+            </h4>
+            <ul className="space-y-2">
+              {menus.map((menu) => (
+                <MenuListItem
+                  key={menu.id}
+                  href={`/${lng}${menu.menuPath}`}
+                  title={getTitle(menu)}
+                  onClick={onItemClick}
+                >
+                  {getDescription(menu)}
+                </MenuListItem>
+              ))}
+            </ul>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+interface MenuListItemProps {
+  href: string;
+  title: string;
+  children?: React.ReactNode;
+  onClick?: () => void;
+}
+
+function MenuListItem({ href, title, children, onClick }: MenuListItemProps) {
+  return (
+    <li>
+      <NavigationMenuLink asChild>
+        <Link
+          href={href}
+          onClick={onClick}
+          className={cn(
+            "block select-none space-y-1 rounded-md p-2 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
+          )}
+        >
+          <div className="text-sm font-medium leading-none">{title}</div>
+          {children && (
+            <p className="line-clamp-2 text-xs leading-snug text-muted-foreground">
+              {children}
+            </p>
+          )}
+        </Link>
+      </NavigationMenuLink>
+    </li>
+  );
+}
+
-- 
cgit v1.2.3

