Files
taxbaik/TaxBaik.Web/Components/Admin/Pages/Blog/BlogList.razor
T
kjh2064 1b173376ee
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m53s
refactor: admin ui를 fluent v5와 html 기반으로 전환
2026-06-29 22:37:40 +09:00

144 lines
5.2 KiB
Plaintext

@page "/admin/blog"
@attribute [Authorize]
@inject IApiClient ApiClient
@inject IJSRuntime JS
<PageTitle>블로그 관리</PageTitle>
<section class="admin-page-hero">
<div>
<div class="admin-eyebrow">Content</div>
<h1 class="admin-page-title">블로그 관리</h1>
<p class="admin-page-subtitle">검색 유입 콘텐츠의 발행 상태와 성과를 관리합니다.</p>
</div>
<button type="button" class="site-button primary" @onclick='() => NavTo("/taxbaik/admin/blog/create")'>새 포스트 작성</button>
</section>
<div class="admin-surface mb-4">
<div class="admin-summary-bar">
<span>전체 포스트: @($"{totalPosts}개")</span>
<span>페이지 @currentPage / @totalPages</span>
</div>
</div>
<div class="admin-surface">
@if (isLoading)
{
<Skeleton Count="6" CssClass="taxbaik-skeleton-grid" />
}
else
{
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>제목</th>
<th>발행</th>
<th>조회수</th>
<th>작성일</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var post in posts)
{
<tr>
<td>@post.Title</td>
<td><label><input type="checkbox" checked="@post.IsPublished" @onchange="@(async e => await TogglePublish(post, (bool)e.Value!))" /> 발행</label></td>
<td>@post.ViewCount</td>
<td>@post.CreatedAt.ToString("yyyy-MM-dd")</td>
<td>
<div class="admin-row-actions">
<a class="site-button secondary" href="@($"/taxbaik/admin/blog/{post.Id}/edit")">수정</a>
<button type="button" class="admin-icon-button danger" @onclick="@(async () => await DeletePost(post.Id))">✕</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="admin-pagination">
<button type="button" class="site-button secondary" disabled="@(currentPage <= 1 || isLoading)" @onclick="PreviousPage">이전</button>
<button type="button" class="site-button secondary" disabled="@(currentPage >= totalPages || isLoading)" @onclick="NextPage">다음</button>
</div>
@code {
[CascadingParameter] private Task<AuthenticationState>? AuthStateTask { get; set; }
private List<TaxBaik.Domain.Entities.BlogPost> posts = [];
private bool isLoading = true;
private int currentPage = 1;
private int totalPages = 1;
private int totalPosts = 0;
private const int PageSize = 20;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && AuthStateTask != null)
{
var authState = await AuthStateTask;
if (authState.User.Identity?.IsAuthenticated == true)
{
await LoadPosts();
StateHasChanged();
}
}
}
private string NavTo(string url) => url;
private async Task LoadPosts()
{
isLoading = true;
try
{
var result = await ApiClient.GetAsync<PagedBlogResponse>($"blog/admin?page={currentPage}&pageSize={PageSize}");
posts = result?.Data ?? [];
totalPosts = result?.Total ?? 0;
totalPages = Math.Max(1, (int)Math.Ceiling(totalPosts / (double)PageSize));
}
catch
{
posts = [];
totalPosts = 0;
totalPages = 1;
}
finally
{
isLoading = false;
}
}
private async Task PreviousPage() { if (currentPage > 1) { currentPage--; await LoadPosts(); } }
private async Task NextPage() { if (currentPage < totalPages) { currentPage++; await LoadPosts(); } }
private async Task TogglePublish(TaxBaik.Domain.Entities.BlogPost post, bool isPublished)
{
var previous = post.IsPublished;
post.IsPublished = isPublished;
var result = await ApiClient.PutAsync<TaxBaik.Domain.Entities.BlogPost>($"blog/{post.Id}", new { post.Title, post.Content, post.CategoryId, post.Tags, post.SeoTitle, post.SeoDescription, post.ThumbnailUrl, IsPublished = isPublished, post.AuthorId });
if (result == null)
{
post.IsPublished = previous;
await JS.InvokeVoidAsync("alert", "발행 상태 변경에 실패했습니다.");
return;
}
await JS.InvokeVoidAsync("alert", "발행 상태가 변경되었습니다.");
}
private async Task DeletePost(int postId)
{
await ApiClient.DeleteAsync($"blog/{postId}");
await JS.InvokeVoidAsync("alert", "포스트가 삭제되었습니다.");
await LoadPosts();
}
private class PagedBlogResponse
{
public List<TaxBaik.Domain.Entities.BlogPost> Data { get; set; } = [];
public int Total { get; set; }
}
}