Step 4: Navigation Reorganization
- Add new 'CRM & 세무관리' nav group (BusinessCenter icon)
- Organize 5 CRM pages: TaxProfiles, TaxFilingSchedules, Contracts, ConsultingActivities, RevenueTrackings
- Reorder nav groups: Dashboard → CRM (default expanded) → Customer → Website → Inquiries → Settings
- Update 'Customer' group label and icons for clarity
- Set expandedCRMGroup=true for immediate visibility
Navigation Structure (Post-change):
`
대시보드
├─ CRM & 세무관리 [EXPANDED]
│ ├─ 세무 프로필 (Assignment icon)
│ ├─ 신고 일정 (CalendarMonth icon)
│ ├─ 계약 관리 (Description icon)
│ ├─ 상담 활동 (ChatBubble icon)
│ └─ 수익 추적 (Receipt icon)
├─ 고객 관리
│ ├─ 고객 카드
│ └─ 세무신고
├─ 홈페이지
│ ├─ 공지사항
│ ├─ FAQ 관리
│ ├─ 블로그 관리
│ └─ 시즌 시뮬레이터
├─ 문의 관리
└─ 설정
`
Design Rationale:
- CRM group positioned first (after dashboard) for workflow priority
- Default expanded = immediate page discovery
- Icons from Material Design Filled set for consistency
- Grouped by business domain, not by data type
Build Status: 0 errors, 3 warnings (existing)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Architecture:
- Create companies table with company_code as unique identifier
- Add company_id foreign key to admin_users for multi-tenant support
- Implement backward compatibility with DEFAULT company for existing users
Core Components:
- Company entity with full CRUD operations
- ICompanyRepository interface following Repository pattern
- CompanyRepository with Dapper implementation
- CompanyService with business logic and validation
- CompanyController with REST API endpoints
Admin UI:
- CompanyForm reusable component (Create/Edit pattern)
- CompanyList.razor with pagination and company overview
- CompanyCreate.razor for registering new companies
- CompanyEdit.razor for managing existing companies with delete
- All pages follow admin-page-hero pattern for consistency
SOLID Principles:
- Single Responsibility: Each component has one reason to change
- Open/Closed: Extensible without modifying existing code
- Interface Segregation: Clean repository and service contracts
- Dependency Inversion: All layers depend on abstractions
Database Migration (V014):
- Creates companies table with active/inactive status
- Assigns existing admin users to DEFAULT company
- Provides foundation for role-based access control
Future Enhancement:
- Admin users can belong to specific companies
- Data filtering based on company_id (multi-tenant isolation)
- Company-based permission model
Core Components:
- Create reusable InquiryForm.razor component following SOLID principles
- Implement InquiryCreate.razor for registering new inquiries (offline, phone)
- Implement InquiryEdit.razor for modifying existing inquiries with delete
- Add DeleteAsync method to InquiryRepository and InquiryService
- Update InquiryList with 'Create' button and Edit link in table
Architecture:
- InquiryForm: Encapsulates form logic, can be reused for create/edit
- Service Layer: All operations go through InquiryService for cache invalidation
- Repository Pattern: Database operations isolated in InquiryRepository
- UI Consistency: Both pages follow admin-page-hero pattern
Features:
- Admin can create inquiries from phone/offline consultations
- Admin can modify inquiry details (name, phone, email, message, status, memo)
- Admin can delete inquiries with confirmation dialog
- All operations update dashboard cache
- Status validation and error handling throughout
Testing:
- Updated FakeInquiryRepository in tests to implement DeleteAsync
- Add 'Remember ID' checkbox for improved UX
- Store username in localStorage when checked
- Restore saved username on login page load
- Remove saved username when checkbox unchecked
- Follow security best practice: save username only, not password
- Create BlogEdit.razor for editing existing posts
- Add admin-page-hero section for consistent navigation
- Implement delete functionality with confirmation dialog
- Add GetByIdAsync method to BlogService to support entity retrieval by ID
- Follow SOLID principles: single responsibility for each component
- Move deployment completion alert to background Task
- Prevent blocking app startup waiting for Telegram API
- Fixes 'service not responding' errors during health check
- Add error handling for Telegram send failures
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Set MaxRequestBodySize to 100MB for large file uploads
- Resolves 'Request Header Or Cookie Too Large' errors
- Applies to Kestrel server in both development and production
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Enhanced topbar with better button styling and tooltips
- Added system information to drawer footer (server, update status)
- Improved visual hierarchy and spacing
- Better responsive design for mobile screens
- Replaced meaningless message with useful admin context
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
InquiryDetail and ClientDetail pages were missing the admin-page-hero
section, causing the loading overlay to remain stuck on navigation.
The loading indicator (admin-session.js) detects page.admin-page-hero
to know when to hide the overlay.
Now all detail pages show smooth loading indicators on navigation.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Apply same EnsureAuthHeader pattern for consistency across all API
clients. Dashboard summary numbers now load correctly with proper JWT
authentication in Blazor Server environment.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Blazor Server components cannot access client-side localStorage, so
InquiryBrowserClient needs to get the access token from server-side
ITokenStore and manually add the Authorization header to requests.
This fixes 401 Unauthorized errors when InquiryList loads inquiry data.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Move MudTabs inside MudPaper always visible structure
- Only render MudTabs content (with data) after isLoading becomes false
- Add null/empty check in InquiryTable.OnParametersSet()
- Add error handling in InquiryList data loading
Previously, MudTabs would render before data loaded, causing child
InquiryTable components to mount with empty Inquiries list. After
data loaded, child components weren't re-rendered because Blazor
didn't detect parameter changes in that scenario.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Page may be already rendered when showLoading() is called (fast nav, cached state)
- Check .admin-page-hero / .admin-login-page immediately and hide if present
- Prevents stuck loading overlay on rapid navigation between pages
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- App.razor: loading overlay starts with `show` class (visible on cold load)
- admin-session.js: add showLoading()/hideLoading(); MutationObserver detects
.admin-page-hero / .admin-login-page instead of mud-element count threshold;
observer restarts on every navigation cycle via LocationChanged
- MainLayout.razor: subscribe to NavigationManager.LocationChanged →
call JS showLoading() on every route change; implements IDisposable
- InquiryList.razor: remove unused IInquiryRepository injection; load data
once (GetPagedAsync(1,200)) and pass IReadOnlyList to all six tab panels
- InquiryTable.razor: accept Inquiries parameter; filter synchronously in
OnParametersSet() — eliminates 6 redundant API calls per page visit
- admin.css: overlay fade-in animation (0.15s); page content fade-in on
route mount via .admin-page-hero / .admin-login-page animation (0.25s)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
**Issue**: Loading indicator remained visible, intercepting all user interactions (pointer-events: auto blocks clicks)
**Root cause**: Multiple detection methods insufficient, race condition between JavaScript execution and Blazor initialization
**Solution**: Add guaranteed 3-second timeout + multiple detection methods
- Method 1: 3000ms timeout (guaranteed)
- Method 2: Detect when 10+ MudBlazor components appear
- Method 3: Hide when readystatechange to 'interactive' or 'complete'
**Failsafe**: Even if Blazor never fires events, loading WILL hide after 3 seconds max
**Result**:
- Loading shows: immediate on page load
- Loading hides: within 1-3 seconds (whichever is first)
- User can interact: guaranteed by 3-second timeout at latest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issue**: Loading indicator (spinner) continues to display even after page fully loads
**Root cause**:
- Blazor.start() was already called by blazor.web.js (auto-starts)
- Calling it again in JavaScript won't trigger promise resolution
- Promise callback never executed, overlay never hidden
**Solution**: Use multiple detection methods to ensure loading hides:
1. Blazor 'ready' event listener (when circuit is ready)
2. DOMContentLoaded + 500ms timeout (fallback)
3. MutationObserver watching for 20+ MudBlazor components
**Result**:
- Loading spinner shows: page load starts
- Spinner hides: when ANY of the above conditions met (whichever is first)
- No more stuck loading indicator
This ensures loading always hides regardless of how Blazor initializes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issue**: White screen appears 1-2 seconds during page load/transitions while Blazor circuit connects
**Solution**: Add loading spinner overlay that displays while Blazor initializes
**Changes**:
1. App.razor: Add loading overlay HTML element
2. admin.css: Add loading spinner styles + animations
3. admin-session.js: Show overlay on load, hide when Blazor circuit ready
**UX Flow**:
- Page load starts → Blazor loading spinner appears
- Blazor circuit connects (~1-2s) → Spinner disappears
- Page fully interactive → User sees content
**Styling**:
- Centered spinner with 'Loading...' text
- Semi-transparent background (blur effect)
- Smooth fade-out when complete
- High z-index (9999) to cover all content
This provides clear visual feedback that the app is working, not frozen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issue**: White screen still appears during page navigation even with prerender: true
**Root cause**: Blazor components (MudGrid, MudPaper, etc.) and their children don't fully render during prerendering phase. Only parent shells render, leaving empty containers.
**Solution**:
- prerender: true → false (App.razor Routes component)
- Pure interactive server rendering (no static prerendering)
- Blazor handles loading state automatically
**UX Result**:
- First page load: Brief loading indicator while Blazor circuit connects (~1-2s)
- Page navigation: Same loading indicator (consistent experience)
- No partial content flashing (no empty containers)
- All Blazor components fully interactive from initial render
This is the correct pattern for Blazor Server apps with complex component trees.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issue**: Pages show white screen briefly before rendering when navigating between pages
**Root cause**: prerender: false in Routes component meant pages weren't statically prerendered before Blazor interactive mode connected, causing delay
**Fix**:
- Changed prerender: false → prerender: true
- Added explicit MudDialogProvider and MudSnackbarProvider for prerendering support
**Result**: Pages now render immediately with initial HTML, Blazor interactivity attached after - no white screen flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the page header section in MudContainer to ensure proper MudBlazor component hierarchy and rendering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issue**: Settings 페이지가 흰 화면으로만 표시됨
**Root cause**: MudGrid 내 MudPaper 요소들의 들여쓰기 누락으로 인한 HTML 구조 손상
- Line 22: MudPaper이 MudItem 없이 렌더링
- Line 50: 동일한 구조 오류
**Fix**: 모든 요소를 올바르게 들여쓰기
- MudPaper > MudForm > MudTextField 계층 정렬
- 모든 자식 요소 2칸 들여쓰기
**Result**: Settings 페이지가 정상 렌더링되고 폼 필드 표시됨
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>