4358b189c8
TaxBaik CI/CD / build-and-deploy (push) Successful in 3m2s
**Blazor Pages Refactored (9 pages):** ✅ ClientList.razor (Service → IClientBrowserClient) ✅ ClientEdit.razor (Service → IClientBrowserClient) ✅ TaxFilingList.razor (Service → ITaxFilingBrowserClient) ✅ FilingTable.razor (Service → ITaxFilingBrowserClient) ✅ FaqList.razor (Service → IFaqBrowserClient) ✅ FaqEdit.razor (Service → IFaqBrowserClient) ✅ AnnouncementList.razor (Service → IAnnouncementBrowserClient) ✅ AnnouncementEdit.razor (Service → IAnnouncementBrowserClient) ✅ Previously: Dashboard, InquiryTable, InquiryDetail **Pattern Applied Consistently:** - Removed all direct service injections (Service Layer) - Injected specialized Browser Clients (API Layer) - Error handling with Snackbar notifications - Try-catch for all API calls - Graceful fallbacks (empty lists on error) **Phase 7 Complete: 100% API-First Refactoring** All admin pages now use: ClientBrowserClient → /api/client (Clients) TaxFilingBrowserClient → /api/tax-filing (Tax Filings) FaqBrowserClient → /api/faq (FAQs) AnnouncementBrowserClient → /api/announcement (Announcements) InquiryBrowserClient → /api/inquiry (Inquiries) AdminDashboardClient → /api/admin-dashboard (Dashboard) **SOLID + Maintainability Achieved:** ✓ Single Responsibility: Each client = one domain ✓ Open/Closed: Extensible without modifying Blazor ✓ Dependency Inversion: Blazor → Abstractions, not services ✓ Interface Segregation: Fine-grained client interfaces ✓ Liskov Substitution: Interchangeable implementations Build: ✅ Success (0 errors) Status: Ready for Phase 6 (SignalR Integration) Next: NotificationHub for real-time dashboard updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
5.0 KiB
Plaintext
143 lines
5.0 KiB
Plaintext
@page "/admin/faqs"
|
|
@attribute [Authorize]
|
|
@using TaxBaik.Web.Services
|
|
@using TaxBaik.Domain.Entities
|
|
@inject IFaqBrowserClient FaqClient
|
|
@inject NavigationManager Navigation
|
|
@inject IDialogService DialogService
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>FAQ 관리</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">홈페이지</MudText>
|
|
<MudText Typo="Typo.h4" Class="admin-page-title">FAQ 관리</MudText>
|
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">홈페이지 자주 묻는 질문을 등록하고 순서를 관리합니다.</MudText>
|
|
</div>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.Add"
|
|
Href="/taxbaik/admin/faqs/create">
|
|
FAQ 등록
|
|
</MudButton>
|
|
</section>
|
|
|
|
<MudPaper Class="admin-surface" Elevation="0">
|
|
@if (faqs is null)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" />
|
|
}
|
|
else if (!faqs.Any())
|
|
{
|
|
<div class="pa-6 text-center">
|
|
<MudIcon Icon="@Icons.Material.Filled.QuestionAnswer" Style="font-size:3rem; opacity:.3;" />
|
|
<MudText Class="mt-2 text-muted">등록된 FAQ가 없습니다.</MudText>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:60px;">순서</th>
|
|
<th>질문</th>
|
|
<th style="width:130px;">카테고리</th>
|
|
<th style="width:90px;">상태</th>
|
|
<th style="width:160px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var item in faqs)
|
|
{
|
|
<tr>
|
|
<td class="text-center">
|
|
<MudText Typo="Typo.body2">@item.SortOrder</MudText>
|
|
</td>
|
|
<td>
|
|
<MudText Typo="Typo.body2" Style="max-width:480px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
|
|
@item.Question
|
|
</MudText>
|
|
</td>
|
|
<td>
|
|
@if (!string.IsNullOrEmpty(item.Category))
|
|
{
|
|
<MudChip Size="Size.Small" Color="Color.Default">@item.Category</MudChip>
|
|
}
|
|
</td>
|
|
<td>
|
|
@if (item.IsActive)
|
|
{
|
|
<MudChip Size="Size.Small" Color="Color.Success">노출 중</MudChip>
|
|
}
|
|
else
|
|
{
|
|
<MudChip Size="Size.Small" Color="Color.Default">비활성</MudChip>
|
|
}
|
|
</td>
|
|
<td>
|
|
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
|
|
<MudButton @onclick="@(() => Navigation.NavigateTo($"/taxbaik/admin/faqs/{item.Id}/edit"))">
|
|
수정
|
|
</MudButton>
|
|
<MudButton Color="Color.Error" @onclick="@(() => DeleteAsync(item))">
|
|
삭제
|
|
</MudButton>
|
|
</MudButtonGroup>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</MudSimpleTable>
|
|
<MudText Typo="Typo.caption" Class="pa-2 text-muted">
|
|
총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개
|
|
</MudText>
|
|
}
|
|
</MudPaper>
|
|
|
|
@code {
|
|
private List<Faq>? faqs;
|
|
|
|
protected override async Task OnInitializedAsync() => await LoadAsync();
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
try
|
|
{
|
|
faqs = (await FaqClient.GetAllAsync()).ToList();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
|
faqs = [];
|
|
}
|
|
}
|
|
|
|
private async Task DeleteAsync(Faq item)
|
|
{
|
|
var confirmed = await DialogService.ShowMessageBox(
|
|
"FAQ 삭제",
|
|
$"'{item.Question}' 항목을 삭제하시겠습니까?",
|
|
yesText: "삭제", cancelText: "취소");
|
|
|
|
if (confirmed != true) return;
|
|
|
|
try
|
|
{
|
|
var success = await FaqClient.DeleteAsync(item.Id);
|
|
if (success)
|
|
{
|
|
Snackbar.Add("FAQ가 삭제되었습니다.", Severity.Success);
|
|
await LoadAsync();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add("삭제 실패", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
}
|