144 lines
5.2 KiB
Plaintext
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; }
|
|
}
|
|
}
|