window.taxbaikAdminSession = { syncRouteClass: function () { document.documentElement.classList.toggle( 'admin-login-route', window.location.pathname.toLowerCase().endsWith('/admin/login')); }, getViewportWidth: function () { return window.innerWidth || document.documentElement.clientWidth || 0; }, clearAuthToken: function () { try { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); localStorage.removeItem('tokenExpiry'); localStorage.removeItem('auth_token'); } catch { // Ignore storage errors; redirect still recovers the session. } }, showLoading: function () { if (document.documentElement.classList.contains('admin-login-route')) { window.taxbaikAdminSession.hideLoading(); return; } const overlay = document.getElementById('blazor-loading'); if (!overlay) return; // Show overlay immediately overlay.classList.add('show'); // Check if page is already ready (cached state on fast nav) const pageReady = document.querySelector('.admin-page-hero') !== null || document.querySelector('.admin-login-page') !== null; if (pageReady) { // Page already rendered, hide immediately window.taxbaikAdminSession.hideLoading(); return; } // Start observer to catch future mutations if (window._taxbaikLoadingObserver) { window._taxbaikLoadingObserver.disconnect(); } window._taxbaikLoadingObserver = new MutationObserver(function () { const pageReady = document.querySelector('.admin-page-hero') !== null || document.querySelector('.admin-login-page') !== null; if (pageReady) { window.taxbaikAdminSession.hideLoading(); } }); window._taxbaikLoadingObserver.observe(document.body, { childList: true, subtree: true }); // Safety fallback: hide after 3 seconds regardless. if (window._taxbaikLoadingTimeout) { clearTimeout(window._taxbaikLoadingTimeout); } window._taxbaikLoadingTimeout = setTimeout(function () { window.taxbaikAdminSession.hideLoading(); }, 3000); }, hideLoading: function () { const overlay = document.getElementById('blazor-loading'); if (overlay) { overlay.classList.remove('show'); } if (window._taxbaikLoadingTimeout) { clearTimeout(window._taxbaikLoadingTimeout); window._taxbaikLoadingTimeout = null; } if (window._taxbaikLoadingObserver) { window._taxbaikLoadingObserver.disconnect(); window._taxbaikLoadingObserver = null; } }, watchReconnect: function () { window.taxbaikAdminSession.syncRouteClass(); window.addEventListener('popstate', window.taxbaikAdminSession.syncRouteClass); if (document.documentElement.classList.contains('admin-login-route')) { window.taxbaikAdminSession.hideLoading(); } // Show loading on initial page load — overlay has 'show' from HTML, // but we still need to set up the observer to detect when to hide it. window.taxbaikAdminSession.showLoading(); const modal = document.getElementById('components-reconnect-modal'); if (!modal) return; const reloadOnRejectedCircuit = function () { const className = modal.className || ''; if (className.includes('components-reconnect-failed') || className.includes('components-reconnect-rejected')) { window.setTimeout(function () { window.location.reload(); }, 1500); } }; new MutationObserver(reloadOnRejectedCircuit) .observe(modal, { attributes: true, attributeFilter: ['class'] }); }, bindLoginForm: function () { const form = document.getElementById('admin-login-form'); if (!form || form.dataset.bound === '1') return; form.dataset.bound = '1'; form.addEventListener('submit', async function (event) { event.preventDefault(); const username = form.querySelector('input[placeholder="사용자명"]')?.value?.trim() || ''; const password = form.querySelector('input[placeholder="비밀번호"]')?.value || ''; const rememberMe = form.querySelector('input[type="checkbox"]')?.checked || false; const existing = form.parentElement.querySelector('.login-error-message'); const submitButton = form.querySelector('button[type="submit"]'); if (existing) existing.remove(); if (submitButton) submitButton.disabled = true; try { const response = await fetch('/taxbaik/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!response.ok) { throw new Error('login failed'); } const data = await response.json(); if (!data?.accessToken || !data?.refreshToken) { throw new Error('invalid response'); } localStorage.setItem('accessToken', data.accessToken); localStorage.setItem('refreshToken', data.refreshToken); localStorage.setItem('tokenExpiry', String(Date.now() + (data.expiresIn || 3600) * 1000)); if (rememberMe) { localStorage.setItem('admin-remembered-username', username); } else { localStorage.removeItem('admin-remembered-username'); } window.location.href = '/taxbaik/admin/dashboard'; } catch { const error = document.createElement('div'); error.className = 'mud-alert mud-alert-filled-error login-error-message mb-4'; error.textContent = '로그인 중 오류가 발생했습니다.'; form.parentElement.insertBefore(error, form); } finally { if (submitButton) submitButton.disabled = false; } }); } }; // 더존 ERP 스타일 엔터 키 포커스 이동 및 단축키 바인딩 document.addEventListener('keydown', function (e) { if (e.key === 'Enter') { const active = document.activeElement; if (!active) return; // 특정 영역(편집 폼 또는 다이얼로그) 내의 입력 필드만 포커스 이동 처리 const container = active.closest('.admin-editor-panel, .mud-form, .mud-dialog'); if (!container) return; // textarea나 button, submit 타입 등은 기본 동작(줄바꿈/제출) 유지 if (active.tagName === 'TEXTAREA' || active.tagName === 'BUTTON' || active.getAttribute('type') === 'submit' || active.classList.contains('mud-button-root')) { return; } e.preventDefault(); // 포커스 이동 가능한 모든 입력 요소 수집 const focusables = Array.from(container.querySelectorAll('input, select, textarea, button')) .filter(el => { const style = window.getComputedStyle(el); return el.tabIndex >= 0 && !el.disabled && el.getAttribute('aria-disabled') !== 'true' && style.display !== 'none' && style.visibility !== 'hidden'; }); const index = focusables.indexOf(active); if (index > -1 && index < focusables.length - 1) { const nextEl = focusables[index + 1]; nextEl.focus(); if (typeof nextEl.select === 'function') { nextEl.select(); } } } });