refactor: 메뉴 트리 스크립트 공통화 및 디자인 통일
- public/js/menu-tree.js 공통 스크립트 생성 - 테이블(tr.menu-row) / div(.menu-item) 둘 다 지원 - toggleChildren, hideChildren, showChildren 함수 통합 - 권한 관리 페이지들 메뉴 트리 디자인 통일 - role-permissions, department-permissions, user-permissions - 폴더/파일 아이콘, 접기/펼치기 버튼, chevron 아이콘 - menu-row 클래스 및 data 속성 추가 - permission-analyze 접기/펼치기 기능 추가 - data-parent-id, data-depth 속성 추가 - 폴더 버튼 클릭으로 하위 메뉴 토글 - menus 페이지 스크립트 공통화 - 각 페이지 중복 코드 제거 및 공통 menu-tree.js 로드
This commit is contained in:
@@ -148,4 +148,5 @@ function reloadPermissions() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@@ -20,16 +20,43 @@
|
||||
$permissionTypes = ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage'];
|
||||
@endphp
|
||||
@forelse($menus as $index => $menu)
|
||||
<tr>
|
||||
<tr class="menu-row"
|
||||
data-menu-id="{{ $menu->id }}"
|
||||
data-parent-id="{{ $menu->parent_id ?? '' }}"
|
||||
data-depth="{{ $menu->depth ?? 0 }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 text-center">
|
||||
{{ $index + 1 }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center text-sm text-gray-900" style="padding-left: {{ ($menu->depth ?? 0) * 20 }}px;">
|
||||
<div class="flex items-center gap-2" style="padding-left: {{ (($menu->depth ?? 0) * 1.5) }}rem;">
|
||||
{{-- 트리 구조 표시 --}}
|
||||
@if(($menu->depth ?? 0) > 0)
|
||||
<span class="mr-2 text-gray-400">└</span>
|
||||
<span class="text-gray-300 text-xs font-mono flex-shrink-0">└─</span>
|
||||
@endif
|
||||
<span>{{ $menu->name }}</span>
|
||||
|
||||
{{-- 폴더/아이템 아이콘 (폴더는 클릭으로 접기/펼치기) --}}
|
||||
@if($menu->has_children)
|
||||
<button type="button"
|
||||
onclick="toggleChildren({{ $menu->id }})"
|
||||
class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outline-none"
|
||||
data-menu-id="{{ $menu->id }}">
|
||||
<svg class="w-4 h-4 transform transition-transform flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 ml-0.5 transform transition-transform chevron-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
@endif
|
||||
|
||||
{{-- 메뉴명 --}}
|
||||
<span class="text-sm {{ ($menu->depth ?? 0) === 0 ? 'font-semibold text-gray-900' : 'font-medium text-gray-700' }}">
|
||||
{{ $menu->name }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
|
||||
@@ -142,50 +142,6 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
});
|
||||
};
|
||||
|
||||
// 자식 메뉴 접기/펼치기
|
||||
window.toggleChildren = function(menuId) {
|
||||
const button = document.querySelector(`.toggle-btn[data-menu-id="${menuId}"]`);
|
||||
const svg = button.querySelector('svg');
|
||||
const isCollapsed = svg.classList.contains('rotate-[-90deg]');
|
||||
|
||||
if (isCollapsed) {
|
||||
// 펼치기
|
||||
svg.classList.remove('rotate-[-90deg]');
|
||||
showChildren(menuId);
|
||||
} else {
|
||||
// 접기
|
||||
svg.classList.add('rotate-[-90deg]');
|
||||
hideChildren(menuId);
|
||||
}
|
||||
};
|
||||
|
||||
// 재귀적으로 자식 메뉴 숨기기
|
||||
function hideChildren(parentId) {
|
||||
const children = document.querySelectorAll(`tr.menu-row[data-parent-id="${parentId}"]`);
|
||||
children.forEach(child => {
|
||||
child.style.display = 'none';
|
||||
const childId = child.getAttribute('data-menu-id');
|
||||
// 하위의 하위도 숨기기
|
||||
hideChildren(childId);
|
||||
});
|
||||
}
|
||||
|
||||
// 재귀적으로 직계 자식만 표시
|
||||
function showChildren(parentId) {
|
||||
const children = document.querySelectorAll(`tr.menu-row[data-parent-id="${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 childSvg = childButton.querySelector('svg');
|
||||
// 자식이 접혀있으면 그 하위는 보여주지 않음
|
||||
if (!childSvg.classList.contains('rotate-[-90deg]')) {
|
||||
showChildren(childId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
||||
@endpush
|
||||
|
||||
@@ -21,24 +21,36 @@
|
||||
{{ $menu->id }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center" style="padding-left: {{ ($menu->depth ?? 0) * 20 }}px;">
|
||||
<div class="flex items-center gap-2" style="padding-left: {{ (($menu->depth ?? 0) * 1.5) }}rem;">
|
||||
{{-- 트리 구조 표시 --}}
|
||||
@if(($menu->depth ?? 0) > 0)
|
||||
<span class="text-gray-300 text-xs font-mono flex-shrink-0">└─</span>
|
||||
@endif
|
||||
|
||||
{{-- 폴더/아이템 아이콘 (폴더는 클릭으로 접기/펼치기) --}}
|
||||
@if($menu->has_children)
|
||||
<button type="button"
|
||||
onclick="toggleChildren({{ $menu->id }})"
|
||||
class="mr-1 text-gray-500 hover:text-gray-700 focus:outline-none toggle-btn"
|
||||
class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outline-none"
|
||||
data-menu-id="{{ $menu->id }}">
|
||||
<svg class="w-4 h-4 transform transition-transform" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-4 h-4 transform transition-transform flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 ml-0.5 transform transition-transform chevron-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<span class="w-4 mr-1"></span>
|
||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
@endif
|
||||
@if(($menu->depth ?? 0) > 0)
|
||||
<span class="mr-2 text-gray-400">└</span>
|
||||
@endif
|
||||
<div>
|
||||
<div class="text-sm font-medium text-gray-900">{{ $menu->name }}</div>
|
||||
|
||||
{{-- 메뉴 정보 --}}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm {{ ($menu->depth ?? 0) === 0 ? 'font-semibold text-gray-900' : 'font-medium text-gray-700' }}">
|
||||
{{ $menu->name }}
|
||||
</span>
|
||||
@if($menu->is_external)
|
||||
<span class="text-xs text-blue-600">(외부)</span>
|
||||
@endif
|
||||
|
||||
@@ -295,4 +295,5 @@ function exportCsv() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
||||
@endpush
|
||||
@@ -2,6 +2,8 @@
|
||||
<div
|
||||
class="menu-item flex items-center gap-2 py-2 rounded-lg border border-transparent cursor-pointer transition-colors hover:bg-gray-50 {{ $menu->depth > 0 ? 'ml-4 border-l-2 border-gray-200' : '' }}"
|
||||
data-menu-id="{{ $menu->id }}"
|
||||
data-parent-id="{{ $menu->parent_id ?? '' }}"
|
||||
data-depth="{{ $menu->depth ?? 0 }}"
|
||||
data-menu-name="{{ $menu->name }}"
|
||||
onclick="selectMenu({{ $menu->id }}, '{{ addslashes($menu->name) }}')"
|
||||
style="padding-left: {{ ($menu->depth * 1.5) + 0.75 }}rem;"
|
||||
@@ -11,11 +13,19 @@ class="menu-item flex items-center gap-2 py-2 rounded-lg border border-transpare
|
||||
<span class="text-gray-300 text-xs font-mono flex-shrink-0">└─</span>
|
||||
@endif
|
||||
|
||||
{{-- 폴더/아이템 아이콘 --}}
|
||||
{{-- 폴더/아이템 아이콘 (폴더는 클릭으로 접기/펼치기) --}}
|
||||
@if($menu->has_children)
|
||||
<svg class="w-4 h-4 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<button type="button"
|
||||
onclick="event.stopPropagation(); toggleChildren({{ $menu->id }})"
|
||||
class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outline-none"
|
||||
data-menu-id="{{ $menu->id }}">
|
||||
<svg class="w-4 h-4 transform transition-transform flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 ml-0.5 transform transition-transform chevron-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
|
||||
@@ -148,4 +148,5 @@ function reloadPermissions() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@@ -20,16 +20,43 @@
|
||||
$permissionTypes = ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage'];
|
||||
@endphp
|
||||
@forelse($menus as $index => $menu)
|
||||
<tr>
|
||||
<tr class="menu-row"
|
||||
data-menu-id="{{ $menu->id }}"
|
||||
data-parent-id="{{ $menu->parent_id ?? '' }}"
|
||||
data-depth="{{ $menu->depth ?? 0 }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 text-center">
|
||||
{{ $index + 1 }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center text-sm text-gray-900" style="padding-left: {{ ($menu->depth ?? 0) * 20 }}px;">
|
||||
<div class="flex items-center gap-2" style="padding-left: {{ (($menu->depth ?? 0) * 1.5) }}rem;">
|
||||
{{-- 트리 구조 표시 --}}
|
||||
@if(($menu->depth ?? 0) > 0)
|
||||
<span class="mr-2 text-gray-400">└</span>
|
||||
<span class="text-gray-300 text-xs font-mono flex-shrink-0">└─</span>
|
||||
@endif
|
||||
<span>{{ $menu->name }}</span>
|
||||
|
||||
{{-- 폴더/아이템 아이콘 (폴더는 클릭으로 접기/펼치기) --}}
|
||||
@if($menu->has_children)
|
||||
<button type="button"
|
||||
onclick="toggleChildren({{ $menu->id }})"
|
||||
class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outline-none"
|
||||
data-menu-id="{{ $menu->id }}">
|
||||
<svg class="w-4 h-4 transform transition-transform flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 ml-0.5 transform transition-transform chevron-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
@endif
|
||||
|
||||
{{-- 메뉴명 --}}
|
||||
<span class="text-sm {{ ($menu->depth ?? 0) === 0 ? 'font-semibold text-gray-900' : 'font-medium text-gray-700' }}">
|
||||
{{ $menu->name }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
|
||||
@@ -169,4 +169,5 @@ function reloadPermissions() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@@ -20,16 +20,43 @@
|
||||
$permissionTypes = ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage'];
|
||||
@endphp
|
||||
@forelse($menus as $index => $menu)
|
||||
<tr>
|
||||
<tr class="menu-row"
|
||||
data-menu-id="{{ $menu->id }}"
|
||||
data-parent-id="{{ $menu->parent_id ?? '' }}"
|
||||
data-depth="{{ $menu->depth ?? 0 }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 text-center">
|
||||
{{ $index + 1 }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center text-sm text-gray-900" style="padding-left: {{ ($menu->depth ?? 0) * 20 }}px;">
|
||||
<div class="flex items-center gap-2" style="padding-left: {{ (($menu->depth ?? 0) * 1.5) }}rem;">
|
||||
{{-- 트리 구조 표시 --}}
|
||||
@if(($menu->depth ?? 0) > 0)
|
||||
<span class="mr-2 text-gray-400">└</span>
|
||||
<span class="text-gray-300 text-xs font-mono flex-shrink-0">└─</span>
|
||||
@endif
|
||||
<span>{{ $menu->name }}</span>
|
||||
|
||||
{{-- 폴더/아이템 아이콘 (폴더는 클릭으로 접기/펼치기) --}}
|
||||
@if($menu->has_children)
|
||||
<button type="button"
|
||||
onclick="toggleChildren({{ $menu->id }})"
|
||||
class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outline-none"
|
||||
data-menu-id="{{ $menu->id }}">
|
||||
<svg class="w-4 h-4 transform transition-transform flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 ml-0.5 transform transition-transform chevron-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
@endif
|
||||
|
||||
{{-- 메뉴명 --}}
|
||||
<span class="text-sm {{ ($menu->depth ?? 0) === 0 ? 'font-semibold text-gray-900' : 'font-medium text-gray-700' }}">
|
||||
{{ $menu->name }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
|
||||
Reference in New Issue
Block a user