feat: [menus] 최상위 그룹 상단/하단 이동 버튼 추가
- depth=0 메뉴에만 이동 버튼(↕) 표시 - 클릭 시 드롭다운으로 상단/하단 이동 선택 - 기존 reorder API 재사용하여 sort_order 일괄 변경
This commit is contained in:
@@ -415,6 +415,97 @@ window.moveMenuGroup = function(groupMenuIds, leadId, newParentId, sortOrder, cs
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 최상위 그룹 상단/하단 이동 =====
|
||||
|
||||
// 이동 드롭다운 표시
|
||||
window.showMoveGroupDropdown = function(menuId, buttonEl) {
|
||||
// 기존 드롭다운 제거
|
||||
const existing = document.getElementById('move-group-dropdown');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
// 같은 버튼 클릭 시 토글 (닫기)
|
||||
if (existing.dataset.menuId === String(menuId)) return;
|
||||
}
|
||||
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.id = 'move-group-dropdown';
|
||||
dropdown.dataset.menuId = menuId;
|
||||
dropdown.className = 'move-group-dropdown';
|
||||
|
||||
dropdown.innerHTML = `
|
||||
<button type="button" onclick="moveGroupToPosition(${menuId}, 'top')" class="move-group-option">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
상단으로 이동
|
||||
</button>
|
||||
<button type="button" onclick="moveGroupToPosition(${menuId}, 'bottom')" class="move-group-option">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
하단으로 이동
|
||||
</button>
|
||||
`;
|
||||
|
||||
// 버튼 위치 기준으로 드롭다운 배치
|
||||
const rect = buttonEl.getBoundingClientRect();
|
||||
dropdown.style.position = 'fixed';
|
||||
dropdown.style.left = rect.left + 'px';
|
||||
dropdown.style.top = (rect.bottom + 4) + 'px';
|
||||
dropdown.style.zIndex = '9999';
|
||||
|
||||
document.body.appendChild(dropdown);
|
||||
|
||||
// 외부 클릭 시 닫기
|
||||
function closeDropdown(e) {
|
||||
if (!dropdown.contains(e.target) && e.target !== buttonEl && !buttonEl.contains(e.target)) {
|
||||
dropdown.remove();
|
||||
document.removeEventListener('click', closeDropdown, true);
|
||||
}
|
||||
}
|
||||
// 다음 이벤트 루프에서 리스너 등록 (현재 클릭 무시)
|
||||
setTimeout(() => document.addEventListener('click', closeDropdown, true), 0);
|
||||
};
|
||||
|
||||
// 최상위 그룹을 상단/하단으로 이동
|
||||
window.moveGroupToPosition = function(menuId, position) {
|
||||
// 드롭다운 닫기
|
||||
const dropdown = document.getElementById('move-group-dropdown');
|
||||
if (dropdown) dropdown.remove();
|
||||
|
||||
// DOM에서 최상위 그룹(depth=0, parent_id="") 수집
|
||||
const allRows = document.querySelectorAll('#menu-sortable tr.menu-row[data-depth="0"][data-parent-id=""]');
|
||||
if (allRows.length === 0) return;
|
||||
|
||||
const groupIds = Array.from(allRows).map(row => parseInt(row.dataset.menuId));
|
||||
const targetIndex = groupIds.indexOf(menuId);
|
||||
if (targetIndex === -1) return;
|
||||
|
||||
// 이미 해당 위치에 있으면 무시
|
||||
if (position === 'top' && targetIndex === 0) return;
|
||||
if (position === 'bottom' && targetIndex === groupIds.length - 1) return;
|
||||
|
||||
// 대상을 배열에서 제거 후 처음/끝에 삽입
|
||||
groupIds.splice(targetIndex, 1);
|
||||
if (position === 'top') {
|
||||
groupIds.unshift(menuId);
|
||||
} else {
|
||||
groupIds.push(menuId);
|
||||
}
|
||||
|
||||
// sort_order 계산 (1부터 순차)
|
||||
const items = groupIds.map((id, index) => ({
|
||||
id: id,
|
||||
sort_order: index + 1
|
||||
}));
|
||||
|
||||
// CSRF 토큰
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||||
|
||||
// 기존 saveMenuOrder API 호출
|
||||
window.saveMenuOrder(items, csrfToken);
|
||||
};
|
||||
|
||||
// 메뉴 순서 저장 API 호출 (같은 레벨)
|
||||
window.saveMenuOrder = function(items, csrfToken) {
|
||||
fetch('/api/admin/menus/reorder', {
|
||||
|
||||
Reference in New Issue
Block a user