revert: rollback Fluent UI and Blazor homepage to last successful state (3be3794)
This commit is contained in:
@@ -5,52 +5,85 @@
|
||||
@using TaxBaik.Domain.Entities
|
||||
@inject IFaqBrowserClient FaqClient
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>@(Id.HasValue ? "FAQ 수정" : "FAQ 등록")</PageTitle>
|
||||
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<div class="admin-eyebrow">홈페이지</div>
|
||||
<h1 class="admin-page-title">@(Id.HasValue ? "FAQ 수정" : "FAQ 등록")</h1>
|
||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">홈페이지</MudText>
|
||||
<MudText Typo="Typo.h4" Class="admin-page-title">@(Id.HasValue ? "FAQ 수정" : "FAQ 등록")</MudText>
|
||||
</div>
|
||||
<button type="button" class="site-button secondary" @onclick='() => Navigation.NavigateTo("/taxbaik/admin/faqs")'>목록으로</button>
|
||||
<MudButton Variant="Variant.Outlined" Href="/taxbaik/admin/faqs"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack">목록으로</MudButton>
|
||||
</section>
|
||||
|
||||
<div class="admin-surface" style="max-width:720px;">
|
||||
<MudPaper Class="admin-surface" Elevation="0" Style="max-width:720px;">
|
||||
@if (isLoading)
|
||||
{
|
||||
<Skeleton Count="4" CssClass="taxbaik-skeleton-grid" />
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<form class="admin-dialog-card" @onsubmit="SaveAsync" @onsubmit:preventDefault="true">
|
||||
<label>질문 * <textarea class="admin-input" rows="3" @bind="faq.Question"></textarea></label>
|
||||
<label>답변 * <textarea class="admin-input" rows="6" @bind="faq.Answer"></textarea></label>
|
||||
<label>카테고리
|
||||
<select class="admin-input" @bind="faq.Category">
|
||||
<option value="">선택하세요</option>
|
||||
@foreach (var cat in FaqService.Categories)
|
||||
{
|
||||
<option value="@cat">@cat</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
<label>정렬 순서 <input class="admin-input" type="number" min="0" max="9999" @bind="SortOrderText" /></label>
|
||||
<label><input type="checkbox" @bind="faq.IsActive" /> 노출 중</label>
|
||||
<div class="admin-dialog-actions">
|
||||
<button type="submit" class="site-button primary" disabled="@isSaving">저장</button>
|
||||
<button type="button" class="site-button secondary" @onclick='() => Navigation.NavigateTo("/taxbaik/admin/faqs")'>취소</button>
|
||||
</div>
|
||||
</form>
|
||||
<MudForm @ref="form" @bind-IsValid="isValid">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="faq.Question"
|
||||
Label="질문 *" Required="true"
|
||||
RequiredError="질문을 입력하세요."
|
||||
Counter="300" MaxLength="300"
|
||||
Lines="2" AutoGrow="true"
|
||||
Placeholder="예: 기장료가 얼마인지 미리 알 수 있나요?" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="faq.Answer"
|
||||
Label="답변 *" Required="true"
|
||||
RequiredError="답변을 입력하세요."
|
||||
Lines="5" AutoGrow="true"
|
||||
Placeholder="방문자에게 보여질 답변을 입력하세요." />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudSelect @bind-Value="faq.Category" Label="카테고리" T="string" Clearable="true">
|
||||
@foreach (var cat in FaqService.Categories)
|
||||
{
|
||||
<MudSelectItem Value="@cat">@cat</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudNumericField @bind-Value="faq.SortOrder"
|
||||
Label="정렬 순서"
|
||||
HelperText="작을수록 위에 노출"
|
||||
Min="0" Max="9999" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3" Class="d-flex align-center">
|
||||
<MudSwitch T="bool" @bind-Value="faq.IsActive" Color="Color.Success"
|
||||
Label="@(faq.IsActive ? "노출 중" : "비활성")" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" Class="d-flex gap-2 mt-2">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Save"
|
||||
OnClick="@SaveAsync" Disabled="@isSaving">
|
||||
@(isSaving ? "저장 중..." : "저장")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Href="/taxbaik/admin/faqs">
|
||||
취소
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
}
|
||||
</div>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[Parameter] public int? Id { get; set; }
|
||||
|
||||
private MudForm form = null!;
|
||||
private Faq faq = new() { SortOrder = 10, IsActive = true };
|
||||
private bool isValid;
|
||||
private bool isLoading = true;
|
||||
private bool isSaving;
|
||||
private string SortOrderText { get => faq.SortOrder.ToString(); set => faq.SortOrder = int.TryParse(value, out var n) ? n : 0; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -61,7 +94,7 @@
|
||||
var existing = await FaqClient.GetByIdAsync(Id.Value);
|
||||
if (existing is null)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "FAQ를 찾을 수 없습니다.");
|
||||
Snackbar.Add("FAQ를 찾을 수 없습니다.", Severity.Error);
|
||||
Navigation.NavigateTo("/taxbaik/admin/faqs");
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +102,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"오류: {ex.Message}");
|
||||
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
||||
Navigation.NavigateTo("/taxbaik/admin/faqs");
|
||||
return;
|
||||
}
|
||||
@@ -79,30 +112,33 @@
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
await form.Validate();
|
||||
if (!isValid) return;
|
||||
|
||||
isSaving = true;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(faq.Question) || string.IsNullOrWhiteSpace(faq.Answer))
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "질문과 답변을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Id.HasValue)
|
||||
{
|
||||
var result = await FaqClient.UpdateAsync(Id.Value, faq);
|
||||
await JS.InvokeVoidAsync("alert", result != null ? "FAQ가 수정되었습니다." : "수정 실패");
|
||||
if (result != null)
|
||||
Snackbar.Add("FAQ가 수정되었습니다.", Severity.Success);
|
||||
else
|
||||
Snackbar.Add("수정 실패", Severity.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await FaqClient.CreateAsync(faq);
|
||||
await JS.InvokeVoidAsync("alert", result != null ? "FAQ가 등록되었습니다." : "등록 실패");
|
||||
if (result != null)
|
||||
Snackbar.Add("FAQ가 등록되었습니다.", Severity.Success);
|
||||
else
|
||||
Snackbar.Add("등록 실패", Severity.Error);
|
||||
}
|
||||
Navigation.NavigateTo("/taxbaik/admin/faqs");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}");
|
||||
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4,63 +4,95 @@
|
||||
@using TaxBaik.Domain.Entities
|
||||
@inject IFaqBrowserClient FaqClient
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>FAQ 관리</PageTitle>
|
||||
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<div class="admin-eyebrow">홈페이지</div>
|
||||
<h1 class="admin-page-title">FAQ 관리</h1>
|
||||
<p class="admin-page-subtitle">홈페이지 자주 묻는 질문을 등록하고 순서를 관리합니다.</p>
|
||||
<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>
|
||||
<a class="site-button primary" href="/taxbaik/admin/faqs/create">FAQ 등록</a>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
Href="/taxbaik/admin/faqs/create">
|
||||
FAQ 등록
|
||||
</MudButton>
|
||||
</section>
|
||||
|
||||
<div class="admin-surface">
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
@if (faqs is null)
|
||||
{
|
||||
<Skeleton Count="5" CssClass="taxbaik-skeleton-grid" />
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else if (!faqs.Any())
|
||||
{
|
||||
<div class="muted">등록된 FAQ가 없습니다.</div>
|
||||
<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
|
||||
{
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:60px;">순서</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 faqs)
|
||||
{
|
||||
<tr>
|
||||
<th>순서</th>
|
||||
<th>질문</th>
|
||||
<th>카테고리</th>
|
||||
<th>상태</th>
|
||||
<th></th>
|
||||
<td class="text-center">
|
||||
<MudText Typo="Typo.body2">@item.SortOrder</MudText>
|
||||
</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>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in faqs)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.SortOrder</td>
|
||||
<td>@item.Question</td>
|
||||
<td>@(string.IsNullOrEmpty(item.Category) ? "" : item.Category)</td>
|
||||
<td><span class="status-pill @(item.IsActive ? "success" : "default")">@(item.IsActive ? "노출 중" : "비활성")</span></td>
|
||||
<td>
|
||||
<div class="admin-actions">
|
||||
<button type="button" class="admin-icon-button" @onclick="@(() => Navigation.NavigateTo($"/taxbaik/admin/faqs/{item.Id}/edit"))">✎</button>
|
||||
<button type="button" class="admin-icon-button danger" @onclick="@(() => DeleteAsync(item))">✕</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="muted mt-2">총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개</div>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
<MudText Typo="Typo.caption" Class="pa-2 text-muted">
|
||||
총 @(faqs.Count)개 · 노출 중 @(faqs.Count(f => f.IsActive))개
|
||||
</MudText>
|
||||
}
|
||||
</div>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
@@ -70,13 +102,16 @@
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && AuthStateTask != null)
|
||||
if (firstRender)
|
||||
{
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
if (AuthStateTask != null)
|
||||
{
|
||||
await LoadAsync();
|
||||
StateHasChanged();
|
||||
var authState = await AuthStateTask;
|
||||
if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
await LoadAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,32 +124,36 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"오류: {ex.Message}");
|
||||
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
||||
faqs = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(Faq item)
|
||||
{
|
||||
var confirmed = await JS.InvokeAsync<bool>("confirm", $"'{item.Question}' 항목을 삭제하시겠습니까?");
|
||||
if (!confirmed) return;
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"FAQ 삭제",
|
||||
$"'{item.Question}' 항목을 삭제하시겠습니까?",
|
||||
yesText: "삭제", cancelText: "취소");
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
try
|
||||
{
|
||||
var success = await FaqClient.DeleteAsync(item.Id);
|
||||
if (success)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "FAQ가 삭제되었습니다.");
|
||||
Snackbar.Add("FAQ가 삭제되었습니다.", Severity.Success);
|
||||
await LoadAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "삭제 실패");
|
||||
Snackbar.Add("삭제 실패", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", $"오류: {ex.Message}");
|
||||
Snackbar.Add($"오류: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user