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
- 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>
- 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>
- 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**: 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>
**Enhancement:**
- Wrap login form in HTML <form> element with @onsubmit
- HTML form automatically treats Enter key as submit action
- No need for custom @onkeypress handler
**Behavior:**
- Users can now press Enter in password field to login
- Or click the login button (existing behavior maintained)
- Both methods trigger HandleLogin() async handler
This provides better UX for keyboard-first users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Issues Fixed:**
1. ✅ Logout not working
- Created Logout.razor page (was missing)
- Properly calls AuthStateProvider.LogoutAsync()
- Redirects to login page with forceLoad: true
- Button click now triggers async logout flow
2. ✅ Accordion state not persisting
- Changed MudNavGroup from fixed Expanded=true/false
- to @bind-Expanded data binding
- Now properly toggles between expanded/collapsed
- State persists across clicks
- Added expandedCustomerGroup, expandedWebsiteGroup properties
3. ✅ Drawer responsiveness
- Already working with @bind-open="@drawerOpen"
- ToggleDrawer() properly toggles state
- Responsive behavior controlled via Breakpoint.Md
**Implementation:**
- Logout.razor: New page for async logout
- Calls AuthStateProvider.LogoutAsync()
- Clears TokenStore + localStorage
- Redirects to /admin/login
- MainLayout.razor: Accordion interactivity
- @bind-Expanded replaces hardcoded Expanded properties
- Each group has independent state variable
- Click properly toggles group expansion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
App.razor already provides MudThemeProvider globally.
Login.razor inheriting from BlankLayout should not redefine it.
This fixes the 'Duplicate MudPopoverProvider detected' error that was
preventing Blazor circuit from establishing and blocking login.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>