/** * 컨텍스트 메뉴 (클릭 메뉴) * 테넌트명, 사용자명 등에서 클릭 시 해당 위치에 메뉴 표시 */ class ContextMenu { constructor() { this.menuElement = null; this.currentTarget = null; this.init(); } init() { // 컨텍스트 메뉴 요소 찾기 this.menuElement = document.getElementById('context-menu'); if (!this.menuElement) return; // 좌클릭 이벤트 등록 (캡처링 - 다른 핸들러보다 먼저 실행) document.addEventListener('click', (e) => this.handleClick(e), true); // ESC 키로 메뉴 닫기 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') this.hide(); }); // 스크롤 시 메뉴 닫기 document.addEventListener('scroll', () => this.hide(), true); } handleClick(e) { const trigger = e.target.closest('[data-context-menu]'); // 메뉴 영역 내 클릭은 무시 (메뉴 항목 클릭 허용) if (e.target.closest('#context-menu')) return; // 트리거가 아니면 메뉴 숨기기 if (!trigger) { this.hide(); return; } e.preventDefault(); e.stopPropagation(); const menuType = trigger.dataset.contextMenu; const entityId = trigger.dataset.entityId; const entityName = trigger.dataset.entityName; this.currentTarget = { type: menuType, id: entityId, name: entityName, element: trigger }; this.show(e.clientX, e.clientY, menuType); } show(x, y, menuType) { if (!this.menuElement) return; // 메뉴 타입에 따라 항목 표시/숨김 this.updateMenuItems(menuType); // 메뉴 표시 this.menuElement.classList.remove('hidden'); // 위치 조정 (화면 밖으로 나가지 않도록) const menuRect = this.menuElement.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; let posX = x; let posY = y; if (x + menuRect.width > viewportWidth) { posX = viewportWidth - menuRect.width - 10; } if (y + menuRect.height > viewportHeight) { posY = viewportHeight - menuRect.height - 10; } this.menuElement.style.left = `${posX}px`; this.menuElement.style.top = `${posY}px`; } hide() { if (this.menuElement) { this.menuElement.classList.add('hidden'); } this.currentTarget = null; } updateMenuItems(menuType) { // 모든 메뉴 항목 숨기기 const allItems = this.menuElement.querySelectorAll('[data-menu-for]'); allItems.forEach(item => { const forTypes = item.dataset.menuFor.split(','); if (forTypes.includes(menuType) || forTypes.includes('all')) { item.classList.remove('hidden'); } else { item.classList.add('hidden'); } }); } // 메뉴 항목 클릭 핸들러 handleMenuAction(action) { if (!this.currentTarget) return; const { type, id, name } = this.currentTarget; switch (action) { case 'view-tenant': if (typeof TenantModal !== 'undefined') { TenantModal.open(id); } break; case 'edit-tenant': window.location.href = `/tenants/${id}/edit`; break; case 'switch-tenant': this.switchTenant(id, name); break; case 'view-user': if (typeof UserModal !== 'undefined') { UserModal.open(id); } break; case 'edit-user': window.location.href = `/users/${id}/edit`; break; case 'delete-user': if (typeof UserModal !== 'undefined') { // 모달 열고 삭제 실행 UserModal.currentUserId = id; UserModal.deleteUser(); } else { // UserModal이 없으면 직접 삭제 확인 if (confirm(`"${name}"을(를) 삭제하시겠습니까?`)) { this.deleteUser(id); } } break; default: console.log('Unknown action:', action, { type, id, name }); } this.hide(); } // 테넌트 전환 async switchTenant(tenantId, tenantName) { try { const form = document.createElement('form'); form.method = 'POST'; form.action = '/tenant/switch'; // CSRF 토큰 const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); const csrfInput = document.createElement('input'); csrfInput.type = 'hidden'; csrfInput.name = '_token'; csrfInput.value = csrfToken; form.appendChild(csrfInput); // 테넌트 ID const tenantInput = document.createElement('input'); tenantInput.type = 'hidden'; tenantInput.name = 'tenant_id'; tenantInput.value = tenantId; form.appendChild(tenantInput); document.body.appendChild(form); form.submit(); } catch (error) { console.error('Failed to switch tenant:', error); showToast('테넌트 전환에 실패했습니다.', 'error'); } } // 사용자 삭제 (fallback) async deleteUser(userId) { try { const response = await fetch(`/api/admin/users/${userId}`, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Accept': 'application/json' } }); const data = await response.json(); if (data.success) { showToast(data.message || '사용자가 삭제되었습니다.', 'success'); // 테이블 새로고침 if (typeof htmx !== 'undefined') { htmx.trigger('#user-table', 'filterSubmit'); } else { window.location.reload(); } } else { showToast(data.message || '삭제에 실패했습니다.', 'error'); } } catch (error) { console.error('Failed to delete user:', error); showToast('삭제에 실패했습니다.', 'error'); } } } // 전역 인스턴스 생성 const contextMenu = new ContextMenu(); // 전역 함수로 노출 (onclick에서 사용) function handleContextMenuAction(action) { contextMenu.handleMenuAction(action); }