- 닫기/수정 버튼을 모달 하단에 고정 (flex-shrink-0) - 콘텐츠 스크롤 시에도 버튼 항상 표시 - 기존 modal-info 내부 버튼 제거 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
252 lines
8.3 KiB
JavaScript
252 lines
8.3 KiB
JavaScript
/**
|
|
* 테넌트 정보 모달
|
|
* 테넌트 상세 정보를 팝업으로 표시하고 탭으로 관련 정보 관리
|
|
*/
|
|
|
|
const TenantModal = {
|
|
modalElement: null,
|
|
currentTenantId: null,
|
|
currentTab: 'info',
|
|
|
|
init() {
|
|
this.modalElement = document.getElementById('tenant-modal');
|
|
if (!this.modalElement) return;
|
|
|
|
// ESC 키로 모달 닫기
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this.isOpen()) {
|
|
this.close();
|
|
}
|
|
});
|
|
|
|
// 배경 클릭 시 모달 닫기
|
|
this.modalElement.addEventListener('click', (e) => {
|
|
if (e.target === this.modalElement) {
|
|
this.close();
|
|
}
|
|
});
|
|
},
|
|
|
|
isOpen() {
|
|
return this.modalElement && !this.modalElement.classList.contains('hidden');
|
|
},
|
|
|
|
async open(tenantId) {
|
|
if (!this.modalElement) {
|
|
console.error('Tenant modal element not found');
|
|
return;
|
|
}
|
|
|
|
this.currentTenantId = tenantId;
|
|
this.currentTab = 'subscription';
|
|
|
|
// 모달 표시
|
|
this.modalElement.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// 로딩 표시
|
|
this.showLoading();
|
|
|
|
// 테넌트 정보 로드
|
|
await this.loadTenantInfo();
|
|
},
|
|
|
|
close() {
|
|
if (this.modalElement) {
|
|
this.modalElement.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
// 푸터 숨기기
|
|
const footer = document.getElementById('tenant-modal-footer');
|
|
if (footer) {
|
|
footer.classList.add('hidden');
|
|
}
|
|
this.currentTenantId = null;
|
|
},
|
|
|
|
showLoading() {
|
|
const content = document.getElementById('tenant-modal-content');
|
|
if (content) {
|
|
content.innerHTML = `
|
|
<div class="flex items-center justify-center h-64">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
},
|
|
|
|
async loadTenantInfo() {
|
|
try {
|
|
const response = await fetch(`/api/admin/tenants/${this.currentTenantId}/modal`, {
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.html) {
|
|
const content = document.getElementById('tenant-modal-content');
|
|
if (content) {
|
|
content.innerHTML = data.html;
|
|
}
|
|
// 푸터 표시
|
|
const footer = document.getElementById('tenant-modal-footer');
|
|
if (footer) {
|
|
footer.classList.remove('hidden');
|
|
}
|
|
// 구독정보 탭 자동 로드
|
|
this.currentTab = ''; // switchTab에서 early return 방지
|
|
await this.switchTab('subscription');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load tenant info:', error);
|
|
this.showError('테넌트 정보를 불러오는데 실패했습니다.');
|
|
}
|
|
},
|
|
|
|
async switchTab(tab) {
|
|
if (this.currentTab === tab) return;
|
|
|
|
this.currentTab = tab;
|
|
|
|
// 탭 버튼 스타일 업데이트
|
|
document.querySelectorAll('#tenant-modal [data-tab-btn]').forEach(btn => {
|
|
if (btn.dataset.tabBtn === tab) {
|
|
btn.classList.add('border-blue-500', 'text-blue-600');
|
|
btn.classList.remove('border-transparent', 'text-gray-500');
|
|
} else {
|
|
btn.classList.remove('border-blue-500', 'text-blue-600');
|
|
btn.classList.add('border-transparent', 'text-gray-500');
|
|
}
|
|
});
|
|
|
|
// 탭 콘텐츠 로드
|
|
const tabContent = document.getElementById('tenant-modal-tab-content');
|
|
if (!tabContent) return;
|
|
|
|
// 로딩 표시
|
|
tabContent.innerHTML = `
|
|
<div class="flex items-center justify-center h-32">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
`;
|
|
|
|
try {
|
|
const response = await fetch(`/api/admin/tenants/${this.currentTenantId}/${tab}`, {
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.html) {
|
|
tabContent.innerHTML = data.html;
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to load ${tab} tab:`, error);
|
|
tabContent.innerHTML = `
|
|
<div class="text-center py-8 text-red-500">
|
|
데이터를 불러오는데 실패했습니다.
|
|
</div>
|
|
`;
|
|
}
|
|
},
|
|
|
|
showError(message) {
|
|
const content = document.getElementById('tenant-modal-content');
|
|
if (content) {
|
|
content.innerHTML = `
|
|
<div class="flex flex-col items-center justify-center h-64 text-red-500">
|
|
<svg class="w-12 h-12 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
<p>${message}</p>
|
|
<button onclick="TenantModal.close()" class="mt-4 px-4 py-2 bg-gray-200 rounded hover:bg-gray-300">
|
|
닫기
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
},
|
|
|
|
// 수정 페이지로 이동
|
|
goToEdit() {
|
|
if (this.currentTenantId) {
|
|
window.location.href = `/tenants/${this.currentTenantId}/edit`;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 모달 내 메뉴 자식 토글
|
|
* @param {number} menuId 부모 메뉴 ID
|
|
*/
|
|
window.toggleModalMenuChildren = function(menuId) {
|
|
const rows = document.querySelectorAll('.modal-menu-row');
|
|
const toggleBtn = document.querySelector(`.modal-menu-toggle[data-menu-id="${menuId}"]`);
|
|
const chevron = toggleBtn?.querySelector('.chevron-icon');
|
|
|
|
// 직접 자식들 찾기
|
|
const directChildren = [];
|
|
rows.forEach(row => {
|
|
if (row.dataset.parentId == menuId) {
|
|
directChildren.push(row);
|
|
}
|
|
});
|
|
|
|
if (directChildren.length === 0) return;
|
|
|
|
// 첫 번째 자식의 현재 상태 확인
|
|
const isHidden = directChildren[0].classList.contains('hidden');
|
|
|
|
if (isHidden) {
|
|
// 펼치기: 직접 자식만 표시
|
|
directChildren.forEach(child => {
|
|
child.classList.remove('hidden');
|
|
});
|
|
// 쉐브론 회전 초기화
|
|
if (chevron) {
|
|
chevron.classList.remove('-rotate-90');
|
|
}
|
|
} else {
|
|
// 접기: 모든 자손 숨기기 (재귀적으로)
|
|
window.hideModalMenuDescendants(menuId, rows);
|
|
// 쉐브론 회전
|
|
if (chevron) {
|
|
chevron.classList.add('-rotate-90');
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 특정 메뉴의 모든 자손 숨기기 (재귀)
|
|
* @param {number} parentId 부모 메뉴 ID
|
|
* @param {NodeList} rows 모든 메뉴 행
|
|
*/
|
|
window.hideModalMenuDescendants = function(parentId, rows) {
|
|
rows.forEach(row => {
|
|
if (row.dataset.parentId == parentId) {
|
|
row.classList.add('hidden');
|
|
// 자식의 쉐브론도 접힌 상태로
|
|
const childToggle = row.querySelector('.modal-menu-toggle');
|
|
const childChevron = childToggle?.querySelector('.chevron-icon');
|
|
if (childChevron) {
|
|
childChevron.classList.add('-rotate-90');
|
|
}
|
|
// 손자들도 재귀적으로 숨기기
|
|
const childMenuId = row.dataset.menuId;
|
|
if (childMenuId) {
|
|
window.hideModalMenuDescendants(childMenuId, rows);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// DOM 로드 시 초기화
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
TenantModal.init();
|
|
}); |