diff --git a/TaxBaik.Web/Components/Admin/App.razor b/TaxBaik.Web/Components/Admin/App.razor index 8fbde11..d77c990 100644 --- a/TaxBaik.Web/Components/Admin/App.razor +++ b/TaxBaik.Web/Components/Admin/App.razor @@ -24,7 +24,7 @@ 배포 또는 서버 재시작 중이면 잠시 후 자동으로 새로고침됩니다. -
+

로드 중...

diff --git a/TaxBaik.Web/Components/Admin/InquiryTable.razor b/TaxBaik.Web/Components/Admin/InquiryTable.razor index a5aed76..b375b8b 100644 --- a/TaxBaik.Web/Components/Admin/InquiryTable.razor +++ b/TaxBaik.Web/Components/Admin/InquiryTable.razor @@ -1,6 +1,3 @@ -@using TaxBaik.Web.Services -@inject IInquiryBrowserClient InquiryClient - @@ -37,24 +34,19 @@ @code { + [Parameter, EditorRequired] + public IReadOnlyList Inquiries { get; set; } = []; + [Parameter] public string Status { get; set; } = ""; - private List inquiries = []; - private List filteredInquiries = []; + private IReadOnlyList filteredInquiries = []; - protected override async Task OnInitializedAsync() - { - var (items, _) = await InquiryClient.GetPagedAsync(1, 100); - inquiries = items.ToList(); - FilterInquiries(); - } - - private void FilterInquiries() + protected override void OnParametersSet() { filteredInquiries = string.IsNullOrEmpty(Status) - ? inquiries - : inquiries.Where(x => x.Status == Status).ToList(); + ? Inquiries + : Inquiries.Where(x => x.Status == Status).ToList(); } private static string GetPreview(string message) @@ -77,9 +69,4 @@ }; private static string GetStatusLabel(string status) => InquiryStatusMapper.Labels.GetValueOrDefault(status, status); - - protected override async Task OnParametersSetAsync() - { - FilterInquiries(); - } } diff --git a/TaxBaik.Web/Components/Admin/Layout/MainLayout.razor b/TaxBaik.Web/Components/Admin/Layout/MainLayout.razor index 29a67ee..592f0e6 100644 --- a/TaxBaik.Web/Components/Admin/Layout/MainLayout.razor +++ b/TaxBaik.Web/Components/Admin/Layout/MainLayout.razor @@ -1,4 +1,7 @@ @inherits LayoutComponentBase +@inject NavigationManager Navigation +@inject IJSRuntime JS +@implements IDisposable @@ -73,8 +76,23 @@ private bool expandedCustomerGroup = true; private bool expandedWebsiteGroup = false; + protected override void OnInitialized() + { + Navigation.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, LocationChangedEventArgs args) + { + _ = InvokeAsync(() => JS.InvokeVoidAsync("taxbaikAdminSession.showLoading")); + } + private void ToggleDrawer() { drawerOpen = !drawerOpen; } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } } diff --git a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor index 549492e..0235a32 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor @@ -1,7 +1,7 @@ @page "/admin/inquiries" @attribute [Authorize] -@using TaxBaik.Domain.Interfaces -@inject IInquiryRepository InquiryRepository +@using TaxBaik.Web.Services +@inject IInquiryBrowserClient InquiryClient 문의 관리 @@ -13,25 +13,44 @@
- - - - - - - - - - - - - - - - - - - - - - +@if (isLoading) +{ + +} +else +{ + + + + + + + + + + + + + + + + + + + + + + +} + +@code { + private bool isLoading = true; + private IReadOnlyList allInquiries = []; + + protected override async Task OnInitializedAsync() + { + var (items, _) = await InquiryClient.GetPagedAsync(1, 200); + allInquiries = items.ToList(); + isLoading = false; + } +} diff --git a/TaxBaik.Web/wwwroot/css/admin.css b/TaxBaik.Web/wwwroot/css/admin.css index b61a6ec..2eab28a 100644 --- a/TaxBaik.Web/wwwroot/css/admin.css +++ b/TaxBaik.Web/wwwroot/css/admin.css @@ -1391,6 +1391,23 @@ textarea:focus-visible { #blazor-loading.show { display: flex; + animation: overlayFadeIn 0.15s ease-out; +} + +@keyframes overlayFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Page content fade-in on each route mount */ +.admin-page-hero, +.admin-login-page { + animation: adminPageIn 0.25s ease-out; +} + +@keyframes adminPageIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } } .blazor-loading-overlay { diff --git a/TaxBaik.Web/wwwroot/js/admin-session.js b/TaxBaik.Web/wwwroot/js/admin-session.js index cc87b6a..5373775 100644 --- a/TaxBaik.Web/wwwroot/js/admin-session.js +++ b/TaxBaik.Web/wwwroot/js/admin-session.js @@ -4,6 +4,7 @@ window.taxbaikAdminSession = { 'admin-login-route', window.location.pathname.toLowerCase().endsWith('/admin/login')); }, + clearAuthToken: function () { try { localStorage.removeItem('auth_token'); @@ -11,54 +12,73 @@ window.taxbaikAdminSession = { // Ignore storage errors; redirect still recovers the session. } }, + + showLoading: function () { + const overlay = document.getElementById('blazor-loading'); + if (!overlay) return; + + // Start observer FIRST so it catches the mutation that brings new content in. + 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 + }); + + // Show overlay after observer is active. + overlay.classList.add('show'); + + // 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); - // Hide loading indicator after Blazor initializes - const loadingOverlay = document.getElementById('blazor-loading'); - if (loadingOverlay) { - const hideLoading = () => { - if (loadingOverlay.classList.contains('show')) { - loadingOverlay.classList.remove('show'); - } - }; - - // Method 1: Hide after 3 seconds (guaranteed timeout) - setTimeout(hideLoading, 3000); - - // Method 2: Hide when Blazor components appear (faster if available) - const observer = new MutationObserver(() => { - const mudElements = document.querySelectorAll('[class*="mud-"]').length; - if (mudElements > 10) { - hideLoading(); - observer.disconnect(); - } - }); - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: true - }); - - // Method 3: Hide when page is interactive - document.addEventListener('readystatechange', () => { - if (document.readyState === 'interactive' || document.readyState === 'complete') { - setTimeout(hideLoading, 500); - } - }); - } + // 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; - } + if (!modal) return; - const reloadOnRejectedCircuit = () => { + const reloadOnRejectedCircuit = function () { const className = modal.className || ''; if (className.includes('components-reconnect-failed') || className.includes('components-reconnect-rejected')) { - window.setTimeout(() => window.location.reload(), 1500); + window.setTimeout(function () { window.location.reload(); }, 1500); } };