모니터링 코드 엔티티 제거 및 API 선택 모듈 추가:
- MonitoringCode 엔티티, 리포지토리, 복합키 클래스 삭제 - API 선택 공용 모듈 JS 및 HTML 파일 추가 (api_selector) - 웹훅 개발 가이드 페이지 신규 추가 (webhook-dev-guide)
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
<!-- Progress Indicator -->
|
||||
<div class="register-progress">
|
||||
<div class="progress-steps">
|
||||
<!-- Step 1: 앱 정보입력 (Completed) -->
|
||||
<!-- Step 1: 앱 정보입력 -->
|
||||
<div class="progress-step-item">
|
||||
<div class="step-icon-wrapper">
|
||||
<div class="step-icon">
|
||||
@@ -64,676 +64,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div th:if="${error}" class="alert alert-error">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||
</svg>
|
||||
<span th:text="${error}"></span>
|
||||
</div>
|
||||
<!-- API 선택 공용 모듈 -->
|
||||
<th:block th:replace="~{fragment/api_selector :: apiSelector(${apiServices}, ${apiKeyModification.selectedApis}, '/myapikey/modify/step2', '/myapikey/modify/step2/save')}"/>
|
||||
|
||||
<!-- Form Content with Sidebar Layout -->
|
||||
<div class="register-form-container with-sidebar">
|
||||
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="apikey-register-sidebar" id="apiSidebar">
|
||||
<nav class="apikey-sidebar-nav">
|
||||
<!-- Sidebar Title -->
|
||||
<div class="sidebar-title">API 서비스 목록</div>
|
||||
|
||||
<!-- All APIs -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-title active" data-group="">
|
||||
<span class="menu-text">전체</span>
|
||||
<span class="api-count"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Categories -->
|
||||
<div class="menu-section" th:each="service : ${apiServices}">
|
||||
<div class="menu-title"
|
||||
th:data-group="${service.id}">
|
||||
<span class="menu-text" th:text="${service.groupName}">서비스명</span>
|
||||
<span class="api-count"></span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button class="apikey-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
|
||||
☰
|
||||
</button>
|
||||
|
||||
<!-- Mobile Overlay -->
|
||||
<div class="apikey-mobile-overlay" id="mobileOverlay"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="apikey-register-content">
|
||||
<form id="modifyStep2Form" method="post" th:action="@{/myapikey/modify/step2}" th:object="${apiKeyModification}" class="register-form">
|
||||
|
||||
<!-- Hidden field for clientId -->
|
||||
<input type="hidden" th:field="*{clientId}"/>
|
||||
|
||||
<!-- Search Box with Select All -->
|
||||
<div class="api-filter-header">
|
||||
<div class="select-all-wrapper" id="selectAllWrapper" style="display: none;">
|
||||
<label class="select-all-label">
|
||||
<input type="checkbox" id="selectAllCheckbox" class="select-all-checkbox visually-hidden">
|
||||
<span class="custom-checkbox"></span>
|
||||
<span id="selectAllText">전체 선택</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="api-search-box">
|
||||
<input type="text"
|
||||
id="apiSearch"
|
||||
class="search-input"
|
||||
placeholder="API 검색...">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.23047 0C14.3285 0 18.4618 4.0539 18.4619 9.05469C18.4619 11.1083 17.7634 13.0014 16.5889 14.5205C16.5913 14.5229 16.5942 14.5249 16.5967 14.5273L19.5498 17.4238C20.1502 18.0131 20.1501 18.9683 19.5498 19.5576C18.949 20.147 17.9748 20.147 17.374 19.5576L14.4209 16.6621C14.3966 16.6383 14.3749 16.6119 14.3525 16.5869C12.8868 17.5478 11.1257 18.1094 9.23047 18.1094C4.13258 18.1092 0 14.0554 0 9.05469C0.000110268 4.05404 4.13265 0.000222701 9.23047 0ZM9.23047 3.01855C5.83201 3.01878 3.07726 5.721 3.07715 9.05469C3.07715 12.3885 5.83194 15.0916 9.23047 15.0918C12.6292 15.0918 15.3848 12.3886 15.3848 9.05469C15.3847 5.72086 12.6291 3.01855 9.23047 3.01855Z" fill="#515961"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Cards Grid -->
|
||||
<div class="api-selection-card-grid" id="apiCardGrid">
|
||||
<!-- Loading state -->
|
||||
<div class="loading-state" id="loadingState" style="grid-column: 1 / -1;">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>API 목록을 불러오는 중...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty state (initially hidden) -->
|
||||
<div class="empty-api-state" id="emptyState" style="display: none; grid-column: 1 / -1;">
|
||||
<div class="empty-icon">📦</div>
|
||||
<h3>API를 선택해주세요</h3>
|
||||
<p>왼쪽 메뉴에서 서비스를 선택하면 해당 API 목록이 표시됩니다.</p>
|
||||
</div>
|
||||
|
||||
<!-- API cards will be dynamically loaded here -->
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
<!-- clientId 는 모듈 폼에 form 속성으로 주입 -->
|
||||
<input type="hidden" name="clientId" th:value="${apiKeyModification.clientId}" form="apiSelectorForm"/>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="form-actions">
|
||||
<button type="button" id="btnPrevStep" class="btn-submit btn-secondary">
|
||||
이전
|
||||
</button>
|
||||
<button type="submit" form="modifyStep2Form" class="btn-submit btn-primary">
|
||||
<button type="submit" form="apiSelectorForm" class="btn-submit btn-primary">
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- Floating Cart Button -->
|
||||
<button type="button" class="floating-cart-btn" id="floatingCartBtn" style="display: none;">
|
||||
<span class="cart-label">선택된 API</span>
|
||||
<span class="cart-badge" id="cartBadge">
|
||||
<span class="cart-count">0</span>
|
||||
<span class="cart-unit">개</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Selected APIs Modal - Uses base modal component -->
|
||||
<div class="modal" id="selectedApisModal" style="display: none;">
|
||||
<div class="modal-backdrop" id="modalOverlay"></div>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">선택된 API 목록</h3>
|
||||
<button type="button" class="modal-close" id="modalCloseBtn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="selected-apis-list" id="modalSelectedList">
|
||||
<!-- Dynamically populated -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" id="modalCancelBtn">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const form = document.getElementById('modifyStep2Form');
|
||||
const searchInput = document.getElementById('apiSearch');
|
||||
const sidebar = document.getElementById('apiSidebar');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
const menuTitles = document.querySelectorAll('.menu-title');
|
||||
const apiCardGrid = document.getElementById('apiCardGrid');
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
|
||||
let currentFilter = ''; // Empty string means "all"
|
||||
let currentServiceName = '전체'; // Store current service name
|
||||
let allApis = []; // Store loaded APIs
|
||||
let allLoadedApis = []; // Store ALL APIs from server for modal display
|
||||
let selectedApis = new Set(); // Track selected API IDs
|
||||
|
||||
// Restore selected APIs from session (for modification)
|
||||
const sessionSelectedApis = /*[[${apiKeyModification.selectedApis}]]*/ [];
|
||||
if (sessionSelectedApis && Array.isArray(sessionSelectedApis)) {
|
||||
sessionSelectedApis.forEach(function(apiId) {
|
||||
selectedApis.add(apiId);
|
||||
});
|
||||
}
|
||||
|
||||
// Load all APIs for modal display (used when APIs from different categories are selected)
|
||||
function loadAllApisForModal() {
|
||||
fetch('/apis/for_request')
|
||||
.then(response => response.json())
|
||||
.then(apis => {
|
||||
allLoadedApis = apis;
|
||||
console.log('Loaded', allLoadedApis.length, 'APIs for modal display');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to load all APIs for modal:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Load APIs via AJAX
|
||||
function loadApis(groupId) {
|
||||
// Show loading state
|
||||
loadingState.style.display = 'block';
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
// Remove existing API cards
|
||||
document.querySelectorAll('.api-selection-card').forEach(card => card.remove());
|
||||
|
||||
// Build URL with optional groupId filter
|
||||
let url = '/apis/for_request';
|
||||
if (groupId) {
|
||||
url += '?groupIds=' + encodeURIComponent(groupId);
|
||||
}
|
||||
|
||||
fetch(url).then(response => response.json()).then(apis => {
|
||||
allApis = apis;
|
||||
loadingState.style.display = 'none';
|
||||
|
||||
if (apis.length === 0) {
|
||||
emptyState.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
renderApiCards(apis);
|
||||
updateSelectAllUI();
|
||||
}).catch(error => {
|
||||
console.error('Failed to load APIs:', error);
|
||||
loadingState.style.display = 'none';
|
||||
emptyState.querySelector('h3').textContent = 'API 로드 실패';
|
||||
emptyState.querySelector('p').textContent = '다시 시도해주세요.';
|
||||
emptyState.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
// Render API cards
|
||||
function renderApiCards(apis) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
apis.forEach(api => {
|
||||
const card = createApiCard(api);
|
||||
fragment.appendChild(card);
|
||||
});
|
||||
|
||||
apiCardGrid.appendChild(fragment);
|
||||
|
||||
// Re-attach event listeners
|
||||
attachCardEventListeners();
|
||||
|
||||
// Update modal list after cards are rendered
|
||||
updateModalList();
|
||||
}
|
||||
|
||||
// Create API card element
|
||||
function createApiCard(api) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'api-selection-card';
|
||||
card.setAttribute('data-group', api.apiGroupId || '');
|
||||
card.setAttribute('data-name', (api.apiName || '').toLowerCase());
|
||||
card.setAttribute('data-desc', (api.apiSimpleDescription || '').toLowerCase());
|
||||
card.setAttribute('data-api-id', api.apiId);
|
||||
|
||||
const isSelected = selectedApis.has(api.apiId);
|
||||
if (isSelected) {
|
||||
card.classList.add('selected');
|
||||
}
|
||||
|
||||
const mainIconHtml = api.mainIcon
|
||||
? `<img src="${api.mainIcon}" alt="${api.apiName}" onerror="this.style.display='none'; this.nextElementSibling.style.display='block'"><i class="fas fa-cube" style="display:none"></i>`
|
||||
: `<i class="fas fa-cube"></i>`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="api-card-content">
|
||||
<div class="api-card-header">
|
||||
<span class="api-card-category">${api.service || '카테고리'}</span>
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox"
|
||||
name="selectedApis"
|
||||
value="${api.apiId}"
|
||||
id="api-${api.apiId}"
|
||||
class="api-checkbox visually-hidden"
|
||||
${isSelected ? 'checked' : ''}>
|
||||
<span class="custom-checkbox"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="api-name">${api.apiName || 'API 이름'}</h3>
|
||||
<p class="api-description">${api.apiSimpleDescription || 'API 설명이 없습니다.'}</p>
|
||||
|
||||
<div class="api-card-icon">
|
||||
${mainIconHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// Attach event listeners to cards
|
||||
function attachCardEventListeners() {
|
||||
const checkboxes = document.querySelectorAll('.api-checkbox');
|
||||
const apiCards = document.querySelectorAll('.api-selection-card');
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
updateCardSelection(this);
|
||||
updateSelectedCount();
|
||||
});
|
||||
});
|
||||
|
||||
apiCards.forEach(card => {
|
||||
card.addEventListener('click', function(e) {
|
||||
// Prevent double toggle if clicking directly on checkbox
|
||||
if (e.target.classList.contains('api-checkbox')) {
|
||||
return;
|
||||
}
|
||||
const checkbox = card.querySelector('.api-checkbox');
|
||||
if (checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
updateCardSelection(checkbox);
|
||||
updateSelectedCount();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update card visual state
|
||||
function updateCardSelection(checkbox) {
|
||||
const card = checkbox.closest('.api-selection-card');
|
||||
if (checkbox.checked) {
|
||||
card.classList.add('selected');
|
||||
selectedApis.add(checkbox.value);
|
||||
} else {
|
||||
card.classList.remove('selected');
|
||||
selectedApis.delete(checkbox.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Update selected count
|
||||
function updateSelectedCount() {
|
||||
// Update floating cart button
|
||||
const floatingCartBtn = document.getElementById('floatingCartBtn');
|
||||
const cartCount = document.querySelector('.cart-count');
|
||||
|
||||
if (selectedApis.size > 0) {
|
||||
floatingCartBtn.style.display = 'flex';
|
||||
cartCount.textContent = selectedApis.size;
|
||||
} else {
|
||||
floatingCartBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Update modal list
|
||||
updateModalList();
|
||||
|
||||
// Update select all checkbox state
|
||||
updateSelectAllCheckboxState();
|
||||
}
|
||||
|
||||
// Update select all UI visibility and text
|
||||
function updateSelectAllUI() {
|
||||
const selectAllWrapper = document.getElementById('selectAllWrapper');
|
||||
const selectAllText = document.getElementById('selectAllText');
|
||||
|
||||
if (currentFilter === '') {
|
||||
// Hide select all when "전체" is selected
|
||||
selectAllWrapper.style.display = 'none';
|
||||
} else {
|
||||
// Show select all with service name
|
||||
selectAllWrapper.style.display = 'flex';
|
||||
selectAllText.textContent = currentServiceName + ' API 전체 선택';
|
||||
}
|
||||
|
||||
updateSelectAllCheckboxState();
|
||||
}
|
||||
|
||||
// Update select all checkbox state based on visible cards
|
||||
function updateSelectAllCheckboxState() {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
const visibleCards = Array.from(document.querySelectorAll('.api-selection-card')).filter(card => card.style.display !== 'none');
|
||||
|
||||
if (visibleCards.length === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleCheckboxes = visibleCards.map(card => card.querySelector('.api-checkbox'));
|
||||
const checkedCount = visibleCheckboxes.filter(cb => cb.checked).length;
|
||||
|
||||
if (checkedCount === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else if (checkedCount === visibleCheckboxes.length) {
|
||||
selectAllCheckbox.checked = true;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update modal selected APIs list
|
||||
function updateModalList() {
|
||||
const modalSelectedList = document.getElementById('modalSelectedList');
|
||||
modalSelectedList.innerHTML = '';
|
||||
|
||||
if (selectedApis.size === 0) {
|
||||
modalSelectedList.innerHTML = '<p class="empty-message">선택된 API가 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Build list using selectedApis Set for consistency
|
||||
selectedApis.forEach(function(apiId) {
|
||||
// Try to find the checkbox in the DOM first
|
||||
const checkbox = document.querySelector('.api-checkbox[value="' + apiId + '"]');
|
||||
let apiName = apiId; // Default to ID if we can't find the name
|
||||
|
||||
if (checkbox) {
|
||||
// If checkbox exists in DOM, get the name from the card
|
||||
const card = checkbox.closest('.api-selection-card');
|
||||
if (card) {
|
||||
const nameElement = card.querySelector('.api-name');
|
||||
if (nameElement) {
|
||||
apiName = nameElement.textContent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If checkbox not in DOM (different category is showing),
|
||||
// try to find the API in our loaded data
|
||||
let api = allApis.find(a => a.apiId === apiId);
|
||||
if (!api && allLoadedApis.length > 0) {
|
||||
api = allLoadedApis.find(a => a.apiId === apiId);
|
||||
}
|
||||
if (api && api.apiName) {
|
||||
apiName = api.apiName;
|
||||
}
|
||||
}
|
||||
|
||||
const apiPill = document.createElement('div');
|
||||
apiPill.className = 'api-pill';
|
||||
apiPill.innerHTML = `
|
||||
<span class="api-pill-name">${apiName}</span>
|
||||
<button type="button" class="api-pill-remove" data-value="${apiId}" aria-label="Remove ${apiName}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
modalSelectedList.appendChild(apiPill);
|
||||
});
|
||||
|
||||
// Add remove handlers
|
||||
document.querySelectorAll('.api-pill-remove').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
const value = this.getAttribute('data-value');
|
||||
const checkbox = document.querySelector('.api-checkbox[value="' + value + '"]');
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
updateCardSelection(checkbox);
|
||||
updateSelectedCount();
|
||||
} else {
|
||||
// If checkbox not found (maybe different category is showing),
|
||||
// still remove from selection
|
||||
selectedApis.delete(value);
|
||||
updateSelectedCount();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
|
||||
const apiCards = document.querySelectorAll('.api-selection-card');
|
||||
apiCards.forEach(function(card) {
|
||||
const apiName = card.getAttribute('data-name');
|
||||
const apiDesc = card.getAttribute('data-desc');
|
||||
|
||||
// Check if matches search
|
||||
const matchesSearch = apiName.includes(searchTerm) || apiDesc.includes(searchTerm);
|
||||
|
||||
if (matchesSearch) {
|
||||
card.style.display = '';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Update select all checkbox state after search
|
||||
updateSelectAllCheckboxState();
|
||||
});
|
||||
}
|
||||
|
||||
// Sidebar category selection
|
||||
menuTitles.forEach(function(title) {
|
||||
title.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove active from all
|
||||
menuTitles.forEach(t => t.classList.remove('active'));
|
||||
// Add active to clicked
|
||||
this.classList.add('active');
|
||||
|
||||
const groupId = this.getAttribute('data-group');
|
||||
currentFilter = groupId;
|
||||
|
||||
// Store service name for select all label
|
||||
currentServiceName = this.textContent.trim().split('\n')[0].trim();
|
||||
|
||||
// Load APIs for selected service
|
||||
loadApis(groupId);
|
||||
|
||||
// Clear search when changing category
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
|
||||
// Close mobile menu if open
|
||||
if (window.innerWidth <= 768 && sidebar.classList.contains('mobile-open')) {
|
||||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile menu toggle
|
||||
function closeMobileMenu() {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
mobileOverlay.classList.remove('active');
|
||||
mobileToggle.innerHTML = '☰';
|
||||
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
|
||||
if (mobileToggle && sidebar && mobileOverlay) {
|
||||
mobileToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
sidebar.classList.toggle('mobile-open');
|
||||
mobileOverlay.classList.toggle('active');
|
||||
|
||||
if (sidebar.classList.contains('mobile-open')) {
|
||||
this.innerHTML = '✕';
|
||||
this.setAttribute('aria-label', '메뉴 닫기');
|
||||
} else {
|
||||
this.innerHTML = '☰';
|
||||
this.setAttribute('aria-label', '메뉴 열기');
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu on overlay click
|
||||
mobileOverlay.addEventListener('click', function() {
|
||||
closeMobileMenu();
|
||||
});
|
||||
}
|
||||
|
||||
// Close mobile menu on resize to desktop
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768 && sidebar.classList.contains('mobile-open')) {
|
||||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Modal control
|
||||
const floatingCartBtn = document.getElementById('floatingCartBtn');
|
||||
const selectedApisModal = document.getElementById('selectedApisModal');
|
||||
const modalOverlay = document.getElementById('modalOverlay');
|
||||
const modalCloseBtn = document.getElementById('modalCloseBtn');
|
||||
const modalCancelBtn = document.getElementById('modalCancelBtn');
|
||||
|
||||
// Open modal
|
||||
function openModal() {
|
||||
selectedApisModal.style.display = 'block';
|
||||
// Add show class to backdrop and modal for visibility
|
||||
setTimeout(function() {
|
||||
if (modalOverlay) {
|
||||
modalOverlay.classList.add('show');
|
||||
}
|
||||
selectedApisModal.classList.add('show');
|
||||
}, 10);
|
||||
document.body.style.overflow = 'hidden'; // Prevent background scroll
|
||||
}
|
||||
|
||||
// Close modal
|
||||
function closeModal() {
|
||||
// Remove show class first for transition
|
||||
if (modalOverlay) {
|
||||
modalOverlay.classList.remove('show');
|
||||
}
|
||||
selectedApisModal.classList.remove('show');
|
||||
|
||||
// Hide modal after transition
|
||||
setTimeout(function() {
|
||||
selectedApisModal.style.display = 'none';
|
||||
}, 300);
|
||||
|
||||
document.body.style.overflow = ''; // Restore scroll
|
||||
}
|
||||
|
||||
// Floating cart button click
|
||||
if (floatingCartBtn) {
|
||||
floatingCartBtn.addEventListener('click', openModal);
|
||||
}
|
||||
|
||||
// Modal overlay click
|
||||
if (modalOverlay) {
|
||||
modalOverlay.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// Modal close button click
|
||||
if (modalCloseBtn) {
|
||||
modalCloseBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// Modal cancel button click
|
||||
if (modalCancelBtn) {
|
||||
modalCancelBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// Close modal on ESC key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && selectedApisModal.style.display === 'block') {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Select All checkbox event
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.addEventListener('change', function() {
|
||||
const isChecked = this.checked;
|
||||
const visibleCards = Array.from(document.querySelectorAll('.api-selection-card')).filter(card => card.style.display !== 'none');
|
||||
|
||||
visibleCards.forEach(function(card) {
|
||||
const checkbox = card.querySelector('.api-checkbox');
|
||||
if (checkbox) {
|
||||
checkbox.checked = isChecked;
|
||||
updateCardSelection(checkbox);
|
||||
}
|
||||
});
|
||||
|
||||
updateSelectedCount();
|
||||
});
|
||||
}
|
||||
|
||||
// Previous step button - save current selections before going back
|
||||
const btnPrevStep = document.getElementById('btnPrevStep');
|
||||
if (btnPrevStep) {
|
||||
btnPrevStep.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Change form action to save endpoint
|
||||
const originalAction = form.action;
|
||||
form.action = '/myapikey/modify/step2/save';
|
||||
|
||||
// Submit the form to save selections
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
// Form validation
|
||||
form.addEventListener('submit', function(e) {
|
||||
// API 선택 필수 검증
|
||||
if (selectedApis.size === 0) {
|
||||
e.preventDefault();
|
||||
customPopups.showAlert('최소 1개 이상의 API를 선택해주세요.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
// Load all APIs for modal display FIRST (async)
|
||||
loadAllApisForModal();
|
||||
|
||||
updateSelectedCount(); // This will show count from restored session data
|
||||
|
||||
// Load all APIs automatically on page load (empty string = all APIs)
|
||||
loadApis('');
|
||||
|
||||
// Log restored selections for debugging
|
||||
if (selectedApis.size > 0) {
|
||||
console.log('Restored ' + selectedApis.size + ' selected APIs from session:', Array.from(selectedApis));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user