Files
taxbaik/TaxBaik.Web/Components/Admin/Pages/Announcements/AnnouncementList.razor
T
kjh2064 4358b189c8
TaxBaik CI/CD / build-and-deploy (push) Successful in 3m2s
refactor: Phase 7-3 Complete - All Blazor pages API-First migration
**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>
2026-06-28 11:15:40 +09:00

171 lines
5.6 KiB
Plaintext

@page "/admin/announcements"
@attribute [Authorize]
@using TaxBaik.Web.Services
@using TaxBaik.Domain.Entities
@inject IAnnouncementBrowserClient AnnouncementClient
@inject NavigationManager Navigation
@inject IDialogService DialogService
@inject ISnackbar Snackbar
<PageTitle>공지사항 관리</PageTitle>
<section class="admin-page-hero">
<div>
<MudText Typo="Typo.caption" Class="admin-eyebrow">Homepage</MudText>
<MudText Typo="Typo.h4" Class="admin-page-title">공지사항 관리</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/announcements/create">
공지 등록
</MudButton>
</section>
<MudPaper Class="admin-surface" Elevation="0">
@if (announcements is null)
{
<MudProgressLinear Indeterminate="true" />
}
else if (!announcements.Any())
{
<MudText Class="pa-4 text-muted">등록된 공지사항이 없습니다.</MudText>
}
else
{
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
<thead>
<tr>
<th>제목</th>
<th>유형</th>
<th>상태</th>
<th>게시 기간</th>
<th>순서</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in announcements)
{
<tr>
<td>@item.Title</td>
<td>
<MudChip Size="Size.Small" Color="@GetTypeColor(item.DisplayType)">
@GetTypeLabel(item.DisplayType)
</MudChip>
</td>
<td>
@if (IsCurrentlyActive(item))
{
<MudChip Size="Size.Small" Color="Color.Success">노출 중</MudChip>
}
else if (!item.IsActive)
{
<MudChip Size="Size.Small" Color="Color.Default">비활성</MudChip>
}
else
{
<MudChip Size="Size.Small" Color="Color.Warning">기간 외</MudChip>
}
</td>
<td class="small">
@FormatPeriod(item)
</td>
<td>@item.SortOrder</td>
<td>
<MudButtonGroup Size="Size.Small" Variant="Variant.Outlined">
<MudButton @onclick="@(() => Navigation.NavigateTo($"/taxbaik/admin/announcements/{item.Id}/edit"))">
수정
</MudButton>
<MudButton Color="Color.Error" @onclick="@(() => DeleteAsync(item))">
삭제
</MudButton>
</MudButtonGroup>
</td>
</tr>
}
</tbody>
</MudSimpleTable>
}
</MudPaper>
@code {
private List<Announcement>? announcements;
protected override async Task OnInitializedAsync()
{
await LoadAsync();
}
private async Task LoadAsync()
{
try
{
announcements = (await AnnouncementClient.GetAllAsync()).ToList();
}
catch (Exception ex)
{
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
announcements = [];
}
}
private async Task DeleteAsync(Announcement item)
{
var confirmed = await DialogService.ShowMessageBox(
"공지 삭제",
$"'{item.Title}' 공지를 삭제하시겠습니까?",
yesText: "삭제", cancelText: "취소");
if (confirmed != true) return;
try
{
var success = await AnnouncementClient.DeleteAsync(item.Id);
if (success)
{
Snackbar.Add("공지사항이 삭제되었습니다.", Severity.Success);
await LoadAsync();
}
else
{
Snackbar.Add("삭제 실패", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
}
}
private static bool IsCurrentlyActive(Announcement a)
{
if (!a.IsActive) return false;
var now = DateTime.UtcNow;
if (a.StartsAt.HasValue && a.StartsAt > now) return false;
if (a.EndsAt.HasValue && a.EndsAt < now) return false;
return true;
}
private static string FormatPeriod(Announcement a)
{
var start = a.StartsAt?.ToLocalTime().ToString("MM/dd") ?? "즉시";
var end = a.EndsAt?.ToLocalTime().ToString("MM/dd") ?? "무기한";
return $"{start} ~ {end}";
}
private static Color GetTypeColor(string type) => type switch
{
"urgent" => Color.Error,
"banner" => Color.Warning,
_ => Color.Info
};
private static string GetTypeLabel(string type) => type switch
{
"urgent" => "긴급",
"banner" => "배너",
_ => "일반"
};
}