From e046dc0a04a28f99bb132dd387e987009ea87f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Fri, 13 Mar 2026 18:14:26 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[menu-tree]=20HTMX=20swap=20=EC=8B=9C=20?= =?UTF-8?q?menu-tree.js=20=EC=A4=91=EB=B3=B5=20=EC=84=A0=EC=96=B8=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - menu-tree.js를 IIFE로 감싸서 const 재선언 에러 방지 - HTMX historyCacheSize를 0으로 설정하여 historyCacheError 방지 --- public/js/menu-tree.js | 208 +++++++++++++------------- resources/views/layouts/app.blade.php | 4 + 2 files changed, 104 insertions(+), 108 deletions(-) diff --git a/public/js/menu-tree.js b/public/js/menu-tree.js index 8dbe70d3..95e8cdfb 100644 --- a/public/js/menu-tree.js +++ b/public/js/menu-tree.js @@ -6,127 +6,119 @@ * - 메뉴 관리 (menus) - 테이블 기반 * * localStorage 키: 'menu-tree-collapsed' (접힌 메뉴 ID 배열) + * IIFE로 감싸서 HTMX swap 시 재실행해도 const 재선언 에러 방지 */ -const MENU_TREE_STORAGE_KEY = 'menu-tree-collapsed'; +(function() { + 'use strict'; -// localStorage에서 접힌 메뉴 ID Set 로드 -function getCollapsedMenuIds() { - try { - const data = localStorage.getItem(MENU_TREE_STORAGE_KEY); - return data ? new Set(JSON.parse(data)) : new Set(); - } catch (e) { - return new Set(); - } -} + const MENU_TREE_STORAGE_KEY = 'menu-tree-collapsed'; -// localStorage에 접힌 메뉴 ID Set 저장 -function saveCollapsedMenuIds(collapsedSet) { - try { - localStorage.setItem(MENU_TREE_STORAGE_KEY, JSON.stringify([...collapsedSet])); - } catch (e) { - // storage full 등 무시 - } -} - -// 자식 메뉴 접기/펼치기 -window.toggleChildren = function(menuId) { - const button = document.querySelector(`.toggle-btn[data-menu-id="${menuId}"]`); - if (!button) return; - - const chevron = button.querySelector('.chevron-icon'); - if (!chevron) return; - - const collapsedSet = getCollapsedMenuIds(); - const isCollapsed = chevron.classList.contains('rotate-[-90deg]'); - - if (isCollapsed) { - // 펼치기 - chevron.classList.remove('rotate-[-90deg]'); - showChildren(menuId); - collapsedSet.delete(String(menuId)); - } else { - // 접기 - chevron.classList.add('rotate-[-90deg]'); - hideChildren(menuId); - collapsedSet.add(String(menuId)); - } - - saveCollapsedMenuIds(collapsedSet); -}; - -// 자식 요소 선택자 (테이블: tr.menu-row, div: .menu-item) -function getChildElements(parentId) { - // 테이블 기반 (tr.menu-row) - let children = document.querySelectorAll(`tr.menu-row[data-parent-id="${parentId}"]`); - if (children.length > 0) return children; - - // div 기반 (.menu-item) - return document.querySelectorAll(`.menu-item[data-parent-id="${parentId}"]`); -} - -// 재귀적으로 자식 메뉴 숨기기 -function hideChildren(parentId) { - const children = getChildElements(parentId); - children.forEach(child => { - child.style.display = 'none'; - const childId = child.getAttribute('data-menu-id'); - hideChildren(childId); - }); -} - -// 전체 접기/펼치기 -window.toggleAllChildren = function(collapse) { - const buttons = document.querySelectorAll('.toggle-btn'); - const collapsedSet = collapse ? new Set() : new Set(); - - buttons.forEach(btn => { - const menuId = btn.getAttribute('data-menu-id'); - const chevron = btn.querySelector('.chevron-icon'); - if (!chevron) return; - - if (collapse) { - chevron.classList.add('rotate-[-90deg]'); - hideChildren(menuId); - collapsedSet.add(String(menuId)); - } else { - chevron.classList.remove('rotate-[-90deg]'); - showChildren(menuId); + function getCollapsedMenuIds() { + try { + const data = localStorage.getItem(MENU_TREE_STORAGE_KEY); + return data ? new Set(JSON.parse(data)) : new Set(); + } catch (e) { + return new Set(); } - }); + } - saveCollapsedMenuIds(collapsedSet); -}; + function saveCollapsedMenuIds(collapsedSet) { + try { + localStorage.setItem(MENU_TREE_STORAGE_KEY, JSON.stringify([...collapsedSet])); + } catch (e) { + // storage full 등 무시 + } + } -// 재귀적으로 직계 자식만 표시 -function showChildren(parentId) { - const children = getChildElements(parentId); - children.forEach(child => { - child.style.display = ''; - const childId = child.getAttribute('data-menu-id'); - const childButton = child.querySelector(`.toggle-btn[data-menu-id="${childId}"]`); - if (childButton) { - const chevron = childButton.querySelector('.chevron-icon'); - if (chevron && !chevron.classList.contains('rotate-[-90deg]')) { - showChildren(childId); + function getChildElements(parentId) { + let children = document.querySelectorAll(`tr.menu-row[data-parent-id="${parentId}"]`); + if (children.length > 0) return children; + return document.querySelectorAll(`.menu-item[data-parent-id="${parentId}"]`); + } + + function hideChildren(parentId) { + const children = getChildElements(parentId); + children.forEach(child => { + child.style.display = 'none'; + const childId = child.getAttribute('data-menu-id'); + hideChildren(childId); + }); + } + + function showChildren(parentId) { + const children = getChildElements(parentId); + children.forEach(child => { + child.style.display = ''; + const childId = child.getAttribute('data-menu-id'); + const childButton = child.querySelector(`.toggle-btn[data-menu-id="${childId}"]`); + if (childButton) { + const chevron = childButton.querySelector('.chevron-icon'); + if (chevron && !chevron.classList.contains('rotate-[-90deg]')) { + showChildren(childId); + } } - } - }); -} + }); + } -// HTMX 리로드 후 접힌 상태 복원 -window.restoreMenuTreeState = function() { - const collapsedSet = getCollapsedMenuIds(); - if (collapsedSet.size === 0) return; - - collapsedSet.forEach(menuId => { + window.toggleChildren = function(menuId) { const button = document.querySelector(`.toggle-btn[data-menu-id="${menuId}"]`); if (!button) return; const chevron = button.querySelector('.chevron-icon'); - if (chevron) { + if (!chevron) return; + + const collapsedSet = getCollapsedMenuIds(); + const isCollapsed = chevron.classList.contains('rotate-[-90deg]'); + + if (isCollapsed) { + chevron.classList.remove('rotate-[-90deg]'); + showChildren(menuId); + collapsedSet.delete(String(menuId)); + } else { chevron.classList.add('rotate-[-90deg]'); hideChildren(menuId); + collapsedSet.add(String(menuId)); } - }); -}; \ No newline at end of file + + saveCollapsedMenuIds(collapsedSet); + }; + + window.toggleAllChildren = function(collapse) { + const buttons = document.querySelectorAll('.toggle-btn'); + const collapsedSet = collapse ? new Set() : new Set(); + + buttons.forEach(btn => { + const menuId = btn.getAttribute('data-menu-id'); + const chevron = btn.querySelector('.chevron-icon'); + if (!chevron) return; + + if (collapse) { + chevron.classList.add('rotate-[-90deg]'); + hideChildren(menuId); + collapsedSet.add(String(menuId)); + } else { + chevron.classList.remove('rotate-[-90deg]'); + showChildren(menuId); + } + }); + + saveCollapsedMenuIds(collapsedSet); + }; + + window.restoreMenuTreeState = function() { + const collapsedSet = getCollapsedMenuIds(); + if (collapsedSet.size === 0) return; + + collapsedSet.forEach(menuId => { + const button = document.querySelector(`.toggle-btn[data-menu-id="${menuId}"]`); + if (!button) return; + + const chevron = button.querySelector('.chevron-icon'); + if (chevron) { + chevron.classList.add('rotate-[-90deg]'); + hideChildren(menuId); + } + }); + }; +})(); \ No newline at end of file diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index df32b6a3..bc27146f 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -126,6 +126,10 @@ })(); +