236 lines
8.4 KiB
Plaintext
236 lines
8.4 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>
|
|
|
|
<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" Elevation="0">
|
|
@if (faqs is null)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" />
|
|
}
|
|
else if (!FilteredFaqs.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:110px;">순서</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 FilteredFaqs)
|
|
{
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-center justify-start gap-1">
|
|
<MudText Typo="Typo.body2" Class="mr-2">@item.SortOrder</MudText>
|
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowDropUp" Size="Size.Small" OnClick="@(() => MoveUpAsync(item))" Style="padding:2px;" Dense="true" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowDropDown" Size="Size.Small" OnClick="@(() => MoveDownAsync(item))" Style="padding:2px;" Dense="true" />
|
|
</div>
|
|
</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">
|
|
검색 결과 @(FilteredFaqs.Count())개 · 총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개
|
|
</MudText>
|
|
}
|
|
</MudPaper>
|
|
|
|
@code {
|
|
[CascadingParameter]
|
|
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
|
|
|
private List<Faq>? faqs;
|
|
private string searchQuery = "";
|
|
|
|
private IEnumerable<Faq> FilteredFaqs => faqs?
|
|
.Where(f => string.IsNullOrEmpty(searchQuery) ||
|
|
f.Question.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) ||
|
|
(f.Answer != null && f.Answer.Contains(searchQuery, StringComparison.OrdinalIgnoreCase)))
|
|
.OrderBy(f => f.SortOrder) ?? Enumerable.Empty<Faq>();
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
if (AuthStateTask != null)
|
|
{
|
|
var authState = await AuthStateTask;
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
await LoadAsync();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
try
|
|
{
|
|
faqs = (await FaqClient.GetAllAsync()).OrderBy(f => f.SortOrder).ToList();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
|
faqs = [];
|
|
}
|
|
}
|
|
|
|
private async Task MoveUpAsync(Faq item)
|
|
{
|
|
if (faqs == null) return;
|
|
var sorted = faqs.OrderBy(f => f.SortOrder).ToList();
|
|
var index = sorted.IndexOf(item);
|
|
if (index <= 0) return;
|
|
|
|
var prev = sorted[index - 1];
|
|
var temp = item.SortOrder;
|
|
item.SortOrder = prev.SortOrder;
|
|
prev.SortOrder = temp;
|
|
|
|
if (item.SortOrder == prev.SortOrder)
|
|
{
|
|
prev.SortOrder = item.SortOrder + 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
await FaqClient.UpdateAsync(item.Id, item);
|
|
await FaqClient.UpdateAsync(prev.Id, prev);
|
|
Snackbar.Add("순서가 상향되었습니다.", Severity.Success);
|
|
await LoadAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"순서 조정 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task MoveDownAsync(Faq item)
|
|
{
|
|
if (faqs == null) return;
|
|
var sorted = faqs.OrderBy(f => f.SortOrder).ToList();
|
|
var index = sorted.IndexOf(item);
|
|
if (index < 0 || index >= sorted.Count - 1) return;
|
|
|
|
var next = sorted[index + 1];
|
|
var temp = item.SortOrder;
|
|
item.SortOrder = next.SortOrder;
|
|
next.SortOrder = temp;
|
|
|
|
if (item.SortOrder == next.SortOrder)
|
|
{
|
|
next.SortOrder = item.SortOrder + 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
await FaqClient.UpdateAsync(item.Id, item);
|
|
await FaqClient.UpdateAsync(next.Id, next);
|
|
Snackbar.Add("순서가 하향되었습니다.", Severity.Success);
|
|
await LoadAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"순서 조정 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|