feat: [menus] 최상위 그룹 상단/하단 이동 버튼 추가

- depth=0 메뉴에만 이동 버튼(↕) 표시
- 클릭 시 드롭다운으로 상단/하단 이동 선택
- 기존 reorder API 재사용하여 sort_order 일괄 변경
This commit is contained in:
김보곤
2026-02-28 08:24:36 +09:00
parent cf7244a30c
commit 90e2f23f18
3 changed files with 138 additions and 6 deletions

View File

@@ -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', {