167 lines
6.1 KiB
Plaintext
167 lines
6.1 KiB
Plaintext
@page "/admin/blog"
|
|
@attribute [Authorize]
|
|
@inject IApiClient ApiClient
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>블로그 관리</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">Content</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.EditNote"
|
|
Href="/taxbaik/admin/blog/create">새 포스트 작성</MudButton>
|
|
</section>
|
|
|
|
<div class="d-flex pa-4 gap-4 align-center">
|
|
<MudTextField @bind-Value="searchQuery" Placeholder="블로그 제목 또는 본문 검색..." Adornment="Adornment.Start"
|
|
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="flex-grow-1" Immediate="true" Clearable="true" />
|
|
</div>
|
|
|
|
<MudPaper Class="admin-surface mb-4" Elevation="0">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
|
<MudText Typo="Typo.subtitle1">@($"검색 결과 {FilteredPosts.Count()}개 / 전체 포스트 {totalPosts}개")</MudText>
|
|
<MudText Typo="Typo.body2">페이지 @currentPage / @totalPages</MudText>
|
|
</MudStack>
|
|
</MudPaper>
|
|
|
|
<MudDataGrid Items="@FilteredPosts" Striped="true" Hoverable="true" Loading="@isLoading" Class="admin-grid">
|
|
<Columns>
|
|
<PropertyColumn Property="x => x.Title" Title="제목" />
|
|
<PropertyColumn Property="x => x.IsPublished" Title="발행">
|
|
<CellTemplate Context="cell">
|
|
<MudCheckBox T="bool" Value="@cell.Item.IsPublished"
|
|
ValueChanged="@(async (bool value) => await TogglePublish(cell.Item, value))" />
|
|
</CellTemplate>
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="x => x.ViewCount" Title="조회수" />
|
|
<PropertyColumn Property="x => x.CreatedAt" Title="작성일" Format="yyyy-MM-dd" />
|
|
<TemplateColumn>
|
|
<CellTemplate Context="cell">
|
|
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Primary"
|
|
Href="@($"/taxbaik/admin/blog/{cell.Item.Id}/edit")">수정하기</MudButton>
|
|
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Error"
|
|
@onclick="@(async () => await DeletePost(cell.Item.Id))">삭제</MudButton>
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
</Columns>
|
|
</MudDataGrid>
|
|
|
|
<MudStack Row="true" Justify="Justify.Center" Class="mt-4" Spacing="2">
|
|
<MudButton Variant="Variant.Outlined" Disabled="@(currentPage <= 1 || isLoading)" @onclick="PreviousPage">이전</MudButton>
|
|
<MudButton Variant="Variant.Outlined" Disabled="@(currentPage >= totalPages || isLoading)" @onclick="NextPage">다음</MudButton>
|
|
</MudStack>
|
|
|
|
@code {
|
|
[CascadingParameter]
|
|
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
|
|
|
private List<TaxBaik.Domain.Entities.BlogPost> posts = [];
|
|
private string searchQuery = "";
|
|
private bool isLoading = true;
|
|
private int currentPage = 1;
|
|
private int totalPages = 1;
|
|
private int totalPosts = 0;
|
|
private const int PageSize = 20;
|
|
|
|
private IEnumerable<TaxBaik.Domain.Entities.BlogPost> FilteredPosts => posts?
|
|
.Where(p => string.IsNullOrEmpty(searchQuery) ||
|
|
p.Title.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) ||
|
|
(p.Content != null && p.Content.Contains(searchQuery, StringComparison.OrdinalIgnoreCase))) ?? Enumerable.Empty<TaxBaik.Domain.Entities.BlogPost>();
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
if (AuthStateTask != null)
|
|
{
|
|
var authState = await AuthStateTask;
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
await LoadPosts();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
isLoading = false;
|
|
}
|
|
|
|
private async Task PreviousPage()
|
|
{
|
|
if (currentPage <= 1)
|
|
return;
|
|
|
|
currentPage--;
|
|
await LoadPosts();
|
|
}
|
|
|
|
private async Task NextPage()
|
|
{
|
|
if (currentPage >= totalPages)
|
|
return;
|
|
|
|
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;
|
|
Snackbar.Add("발행 상태 변경에 실패했습니다.", Severity.Error);
|
|
return;
|
|
}
|
|
|
|
Snackbar.Add("발행 상태가 변경되었습니다.", Severity.Success);
|
|
}
|
|
|
|
private async Task DeletePost(int postId)
|
|
{
|
|
await ApiClient.DeleteAsync($"blog/{postId}");
|
|
Snackbar.Add("포스트가 삭제되었습니다.", Severity.Success);
|
|
await LoadPosts();
|
|
}
|
|
|
|
private class PagedBlogResponse
|
|
{
|
|
public List<TaxBaik.Domain.Entities.BlogPost> Data { get; set; } = [];
|
|
public int Total { get; set; }
|
|
}
|
|
}
|