revert: rollback Fluent UI and Blazor homepage to last successful state (3be3794)

This commit is contained in:
2026-06-30 20:29:42 +09:00
parent 488b8d11b7
commit 54c179b1eb
69 changed files with 3996 additions and 2904 deletions
+165 -163
View File
@@ -8,205 +8,207 @@
<section class="admin-page-hero">
<div>
<div class="admin-eyebrow">Overview</div>
<h1 class="admin-page-title">대시보드</h1>
<p class="admin-page-subtitle">문의 흐름과 콘텐츠 상태를 한 화면에서 확인합니다.</p>
<MudText Typo="Typo.caption" Class="admin-eyebrow">Overview</MudText>
<MudText Typo="Typo.h4" Class="admin-page-title">대시보드</MudText>
<MudText Typo="Typo.body2" Class="admin-page-subtitle">문의 흐름과 콘텐츠 상태를 한 화면에서 확인합니다.</MudText>
</div>
<button type="button" class="site-button primary" @onclick='() => Nav.NavigateTo("/taxbaik/admin/blog/create")'>새 포스트 작성</button>
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" Href="/taxbaik/admin/blog/create">
새 포스트 작성
</MudButton>
</section>
@if (summary is null)
{
<div class="admin-metric-grid">
<Skeleton Count="4" CssClass="taxbaik-skeleton-grid" />
</div>
<div class="admin-surface mt-4">
<Skeleton Count="4" CssClass="taxbaik-skeleton-grid" />
</div>
<div class="admin-surface mt-4">
<Skeleton Count="4" CssClass="taxbaik-skeleton-grid" />
</div>
}
else
{
<div class="admin-metric-grid">
<div class="admin-metric-card accent-blue cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries"))'>
<div class="metric-card-inner">
<span class="metric-label">이번달 문의</span>
<div class="metric-value-row">
<span class="metric-value blue">@summary.ThisMonthInquiries</span>
<span class="metric-icon">💬</span>
</div>
<span class="metric-hint">월간 상담 유입</span>
</div>
</div>
<div class="admin-metric-card accent-amber cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries?status=new"))'>
<div class="metric-card-inner">
<span class="metric-label">신규 문의</span>
<div class="metric-value-row">
<span class="metric-value amber">@summary.NewInquiries</span>
<span class="metric-icon">⚠️</span>
</div>
<span class="metric-hint">처리 대기</span>
</div>
</div>
<div class="admin-metric-card accent-slate cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/blog"))'>
<div class="metric-card-inner">
<span class="metric-label">전체 포스트</span>
<div class="metric-value-row">
<span class="metric-value slate">@summary.TotalPosts</span>
<span class="metric-icon">📄</span>
</div>
<span class="metric-hint">콘텐츠 자산</span>
</div>
</div>
<div class="admin-metric-card accent-green cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/blog"))'>
<div class="metric-card-inner">
<span class="metric-label">발행된 포스트</span>
<div class="metric-value-row">
<span class="metric-value green">@summary.PublishedPosts</span>
<span class="metric-icon">🌐</span>
</div>
<span class="metric-hint">검색 노출 대상</span>
<!-- Metrics Grid - Pure HTML div instead of MudGrid to ensure proper layout -->
<div class="admin-metric-grid">
<div class="admin-metric-card accent-blue cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries"))' style="cursor: pointer;">
<div style="display: flex; flex-direction: column; gap: 12px; height: 100%;">
<span style="font-size: 0.75rem; color: #999; text-transform: uppercase; font-weight: 600;">이번달 문의</span>
<div style="display: flex; justify-content: space-between; align-items: center; flex: 1;">
<span style="font-size: 2rem; font-weight: 700; color: #1565c0;">@summary.ThisMonthInquiries</span>
<span style="font-size: 2.5rem; opacity: 0.15; color: #1976d2;">💬</span>
</div>
<span style="font-size: 0.9rem; color: #666;">월간 상담 유입 (클릭 시 이동)</span>
</div>
</div>
}
@if (upcomingFilings.Count == 0)
<div class="admin-metric-card accent-amber cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries?status=new"))' style="cursor: pointer;">
<div style="display: flex; flex-direction: column; gap: 12px; height: 100%;">
<span style="font-size: 0.75rem; color: #999; text-transform: uppercase; font-weight: 600;">신규 문의</span>
<div style="display: flex; justify-content: space-between; align-items: center; flex: 1;">
<span style="font-size: 2rem; font-weight: 700; color: #e65100;">@summary.NewInquiries</span>
<span style="font-size: 2.5rem; opacity: 0.15; color: #f57c00;">⚠️</span>
</div>
<span style="font-size: 0.9rem; color: #666;">처리 대기 (클릭 시 이동)</span>
</div>
</div>
<div class="admin-metric-card accent-slate cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/blog"))' style="cursor: pointer;">
<div style="display: flex; flex-direction: column; gap: 12px; height: 100%;">
<span style="font-size: 0.75rem; color: #999; text-transform: uppercase; font-weight: 600;">전체 포스트</span>
<div style="display: flex; justify-content: space-between; align-items: center; flex: 1;">
<span style="font-size: 2rem; font-weight: 700; color: #455a64;">@summary.TotalPosts</span>
<span style="font-size: 2.5rem; opacity: 0.15; color: #607d8b;">📄</span>
</div>
<span style="font-size: 0.9rem; color: #666;">콘텐츠 자산 (클릭 시 이동)</span>
</div>
</div>
<div class="admin-metric-card accent-green cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/blog"))' style="cursor: pointer;">
<div style="display: flex; flex-direction: column; gap: 12px; height: 100%;">
<span style="font-size: 0.75rem; color: #999; text-transform: uppercase; font-weight: 600;">발행된 포스트</span>
<div style="display: flex; justify-content: space-between; align-items: center; flex: 1;">
<span style="font-size: 2rem; font-weight: 700; color: #2e7d32;">@summary.PublishedPosts</span>
<span style="font-size: 2.5rem; opacity: 0.15; color: #388e3c;">🌐</span>
</div>
<span style="font-size: 0.9rem; color: #666;">검색 노출 대상 (클릭 시 이동)</span>
</div>
</div>
</div>
@if (upcomingFilings.Count > 0)
{
<div class="admin-surface mt-4">이번 달 마감 임박 신고가 없습니다.</div>
}
else
{
<div class="admin-surface mt-4">
<MudPaper Class="admin-surface mt-4" Elevation="0">
<div class="admin-section-header">
<div>
<h3 class="admin-section-title">이번 달 마감 임박 신고</h3>
<p class="muted">30일 이내 신고 예정 건</p>
<MudText Typo="Typo.h6">이번 달 마감 임박 신고</MudText>
<MudText Typo="Typo.body2">30일 이내 신고 예정 건 (고객명 클릭 시 상세 카드로 연결)</MudText>
</div>
<a class="site-button secondary" href="/taxbaik/admin/tax-filings">전체 일정 보기</a>
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Href="/taxbaik/admin/tax-filings">전체 일정 보기</MudButton>
</div>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
<thead>
<tr>
<th>고객</th>
<th>신고 유형</th>
<th>기한</th>
<th>D-day</th>
</tr>
</thead>
<tbody>
@foreach (var f in upcomingFilings)
{
var dday = (f.DueDate.Date - DateTime.Today).Days;
<tr>
<th>고객</th>
<th>신고 유형</th>
<th>기한</th>
<th>D-day</th>
<td>
<MudLink Href="@($"/taxbaik/admin/clients/{f.ClientId}")" Underline="Underline.Hover" Color="Color.Primary" Class="font-weight-bold">
@f.ClientName
</MudLink>
</td>
<td>@f.FilingType</td>
<td>@f.DueDate.ToString("yyyy-MM-dd")</td>
<td>
@if (dday < 0)
{
<MudChip T="string" Size="Size.Small" Color="Color.Dark">기한 초과 (@(-dday)일)</MudChip>
}
else if (dday <= 7)
{
<MudChip T="string" Size="Size.Small" Color="Color.Error">D-@dday</MudChip>
}
else
{
<span>D-@dday</span>
}
</td>
</tr>
</thead>
<tbody>
@foreach (var f in upcomingFilings)
{
var dday = (f.DueDate.Date - DateTime.Today).Days;
<tr>
<td><a href="@($"/taxbaik/admin/clients/{f.ClientId}")">@f.ClientName</a></td>
<td>@f.FilingType</td>
<td>@f.DueDate.ToString("yyyy-MM-dd")</td>
<td>
@if (dday < 0)
{
<span class="status-pill dark">기한 초과 (@(-dday)일)</span>
}
else if (dday <= 7)
{
<span class="status-pill danger">D-@dday</span>
}
else
{
<span>D-@dday</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
</tbody>
</MudSimpleTable>
</MudPaper>
}
@if (summary is not null)
{
<div class="admin-surface mt-4">
<div class="admin-section-header">
<div>
<h3 class="admin-section-title">최근 문의</h3>
<p class="muted">최근 유입된 상담 요청을 빠르게 확인합니다.</p>
</div>
<a class="site-button secondary" href="/taxbaik/admin/inquiries">문의 전체 보기</a>
</div>
<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 inquiry in summary.RecentInquiries)
{
<tr>
<td><a href="@($"/taxbaik/admin/inquiries/{inquiry.Id}")">@inquiry.Name</a></td>
<td>@inquiry.Phone</td>
<td>@inquiry.ServiceType</td>
<td><span class="status-pill @GetStatusClass(inquiry.Status)">@GetStatusLabel(inquiry.Status)</span></td>
<td>@inquiry.CreatedAt.ToString("yyyy-MM-dd")</td>
</tr>
}
</tbody>
</table>
<MudPaper Class="admin-surface mt-4" Elevation="0">
<div class="admin-section-header">
<div>
<MudText Typo="Typo.h6">최근 문의</MudText>
<MudText Typo="Typo.body2">최근 유입된 상담 요청을 빠르게 확인합니다. (이름 클릭 시 상세 관리 화면으로 연계)</MudText>
</div>
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Href="/taxbaik/admin/inquiries">문의 전체 보기</MudButton>
</div>
}
<MudSimpleTable Striped="true" Dense="true" Class="admin-table">
<thead>
<tr>
<th>이름</th>
<th>전화</th>
<th>분야</th>
<th>상태</th>
<th>날짜</th>
</tr>
</thead>
<tbody>
@foreach (var inquiry in summary.RecentInquiries)
{
<tr>
<td>
<MudLink Href="@($"/taxbaik/admin/inquiries?id={inquiry.Id}")" Underline="Underline.Hover" Color="Color.Primary" Class="font-weight-bold">
@inquiry.Name
</MudLink>
</td>
<td>@inquiry.Phone</td>
<td>@inquiry.ServiceType</td>
<td>
<MudChip T="string" Size="Size.Small" Color="@StatusColor(inquiry.Status)">
@GetStatusLabel(inquiry.Status)
</MudChip>
</td>
<td>@inquiry.CreatedAt.ToString("yyyy-MM-dd")</td>
</tr>
}
</tbody>
</MudSimpleTable>
</MudPaper>
@code {
[CascadingParameter]
private Task<AuthenticationState>? AuthStateTask { get; set; }
private AdminDashboardSummary? summary;
private AdminDashboardSummary summary = new(0, 0, 0, 0, []);
private List<Domain.Entities.TaxFiling> upcomingFilings = [];
private string? errorMessage;
private bool isLoading = true;
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)
{
try
var authState = await AuthStateTask;
if (authState.User.Identity?.IsAuthenticated == true)
{
var summaryTask = DashboardClient.GetSummaryAsync();
var filingsTask = DashboardClient.GetUpcomingFilingsAsync(30);
await Task.WhenAll(summaryTask, filingsTask);
summary = await summaryTask;
upcomingFilings = (await filingsTask).ToList();
try
{
// API 클라이언트 사용 (서비스 직접 호출 X)
var summaryTask = DashboardClient.GetSummaryAsync();
var filingsTask = DashboardClient.GetUpcomingFilingsAsync(30);
await Task.WhenAll(summaryTask, filingsTask);
summary = await summaryTask;
upcomingFilings = (await filingsTask).ToList();
}
catch (Exception ex)
{
errorMessage = "대시보드 데이터를 불러올 수 없습니다.";
Console.Error.WriteLine($"Dashboard error: {ex.Message}");
}
finally
{
isLoading = false;
StateHasChanged();
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Dashboard error: {ex.Message}");
}
StateHasChanged();
}
}
}
private static string GetStatusLabel(string status) => InquiryStatusMapper.Labels.GetValueOrDefault(status, status);
private static string GetStatusClass(string status) => status switch
private static Color StatusColor(string status) => status switch
{
"new" => "warning",
"consulting" => "info",
"contracted" => "success",
"rejected" => "danger",
"closed" => "dark",
_ => "default"
"new" => Color.Warning,
"consulting" => Color.Info,
"contracted" => Color.Success,
"rejected" => Color.Error,
"closed" => Color.Dark,
_ => Color.Default
};
}