feat(admin): 공지사항, FAQ, 블로그 목록 검색 필터 추가 및 블로그 미리보기 탭 탑재, FAQ 순서 조정 기능 구현

This commit is contained in:
2026-07-01 10:53:55 +09:00
parent 24ecf89028
commit d24f3f58db
5 changed files with 159 additions and 23 deletions
@@ -22,16 +22,21 @@
</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 (!faqs.Any())
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>
<MudText Class="mt-2 text-muted">검색 조건에 맞는 FAQ가 없습니다.</MudText>
</div>
}
else
@@ -39,7 +44,7 @@
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
<thead>
<tr>
<th style="width:60px;">순서</th>
<th style="width:110px;">순서</th>
<th>질문</th>
<th style="width:130px;">카테고리</th>
<th style="width:90px;">상태</th>
@@ -47,11 +52,15 @@
</tr>
</thead>
<tbody>
@foreach (var item in faqs)
@foreach (var item in FilteredFaqs)
{
<tr>
<td class="text-center">
<MudText Typo="Typo.body2">@item.SortOrder</MudText>
<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;">
@@ -77,10 +86,10 @@
<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>
@@ -89,7 +98,7 @@
</tbody>
</MudSimpleTable>
<MudText Typo="Typo.caption" Class="pa-2 text-muted">
총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개
검색 결과 @(FilteredFaqs.Count())개 · 총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개
</MudText>
}
</MudPaper>
@@ -99,6 +108,13 @@
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)
{
@@ -120,7 +136,7 @@
{
try
{
faqs = (await FaqClient.GetAllAsync()).ToList();
faqs = (await FaqClient.GetAllAsync()).OrderBy(f => f.SortOrder).ToList();
}
catch (Exception ex)
{
@@ -129,6 +145,66 @@
}
}
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(