Replace admin MudBlazor shell with plain HTML
TaxBaik CI/CD / build-and-deploy (push) Successful in 3m29s

This commit is contained in:
2026-07-08 00:32:07 +09:00
parent 41d0ef4165
commit ece81306fb
3 changed files with 26 additions and 268 deletions
@@ -1,240 +1,10 @@
@inject TaxBaik.Web.Client.Components.Admin.Services.IAdminDashboardClient DashboardClient
@inject NavigationManager Navigation
<PageTitle>대시보드</PageTitle> <PageTitle>대시보드</PageTitle>
<AdminPageHeader Title="대시보드" Eyebrow="Home" Subtitle="운영 현황과 바로가기 작업을 한 화면에서 확인합니다."> <AdminPageHeader Title="대시보드" Eyebrow="Home" Subtitle="관리자 대시보드 초기화 문제를 분리하는 최소 렌더 화면입니다." />
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh" Disabled="@loading" @onclick="ReloadAsync">
새로고침
</MudButton>
</AdminPageHeader>
<MudGrid Class="admin-dashboard-grid">
@if (loading)
{
<MudItem xs="12">
<AdminDataPanel Loading="true" />
</MudItem>
}
else
{
<MudItem xs="12" sm="6" md="3">
<AdminMetricCard Label="이번 달 문의" Value="@summary?.ThisMonthInquiries" Caption="이번 달 접수된 문의" Accent="accent-blue" Icon="@Icons.Material.Filled.MarkUnreadChatAlt" />
</MudItem>
<MudItem xs="12" sm="6" md="3">
<AdminMetricCard Label="신규 문의" Value="@summary?.NewInquiries" Caption="처리 대기 중인 문의" Accent="accent-amber" Icon="@Icons.Material.Filled.PriorityHigh" />
</MudItem>
<MudItem xs="12" sm="6" md="3">
<AdminMetricCard Label="총 게시글" Value="@summary?.TotalPosts" Caption="홈페이지 블로그 누적" Accent="accent-slate" Icon="@Icons.Material.Filled.Article" />
</MudItem>
<MudItem xs="12" sm="6" md="3">
<AdminMetricCard Label="게시중 글" Value="@summary?.PublishedPosts" Caption="현재 공개 중인 글" Accent="accent-green" Icon="@Icons.Material.Filled.Public" />
</MudItem>
<MudItem xs="12" md="5">
<AdminDataPanel> <AdminDataPanel>
<AdminPageHeader Title="빠른 작업" Subtitle="자주 쓰는 관리 화면으로 바로 이동합니다." />
<MudStack Spacing="1">
@foreach (var action in QuickActions)
{
<MudButton Variant="Variant.Outlined" Color="Color.Primary" FullWidth="true" StartIcon="@action.Icon" OnClick="@(() => Navigation.NavigateTo(action.Href))">
@action.Label
</MudButton>
}
</MudStack>
</AdminDataPanel>
</MudItem>
<MudItem xs="12" md="7">
<AdminDataPanel>
<AdminPageHeader Title="최근 문의" Subtitle="최신 처리 흐름을 빠르게 확인합니다." />
@if (summary?.RecentInquiries is null || !summary.RecentInquiries.Any())
{
<AdminEmptyState Icon="@Icons.Material.Filled.Inbox" Message="새로운 문의가 들어오면 이 영역에 표시됩니다." />
}
else
{
<MudSimpleTable Dense="true" Class="admin-table">
<thead>
<tr>
<th>제목</th>
<th>상태</th>
<th>접수일</th>
</tr>
</thead>
<tbody>
@foreach (var inquiry in summary.RecentInquiries)
{
<tr>
<td>@inquiry.Name</td>
<td>@inquiry.Status</td>
<td>@inquiry.CreatedAt.ToString("yyyy-MM-dd")</td>
</tr>
}
</tbody>
</MudSimpleTable>
}
</AdminDataPanel>
</MudItem>
<MudItem xs="12" md="6">
<AdminDataPanel Loading="@statsLoading">
<AdminPageHeader Title="월간 통계" Subtitle="문의 처리 전환과 상태 분포를 확인합니다." />
@if (monthlyStats is not null)
{
<div class="admin-stats-grid">
<div class="admin-stat">
<span class="admin-stat-label">총 문의</span>
<strong class="admin-stat-value">@monthlyStats.TotalInquiries</strong>
</div>
<div class="admin-stat">
<span class="admin-stat-label">상담 중</span>
<strong class="admin-stat-value">@monthlyStats.ConsultingCount</strong>
</div>
<div class="admin-stat">
<span class="admin-stat-label">완료</span>
<strong class="admin-stat-value">@monthlyStats.CompletedCount</strong>
</div>
<div class="admin-stat">
<span class="admin-stat-label">완료율</span>
<strong class="admin-stat-value">@monthlyStats.CompletionRate.ToString("0.#")%</strong>
</div>
</div>
<div class="admin-chart">
<div class="admin-chart-header">
<span>최근 6개월 문의 추세</span>
<span>@monthlyStats.Month</span>
</div>
<div class="admin-chart-bars">
@foreach (var point in trendPoints)
{
<div class="admin-chart-row">
<span>@point.Label</span>
<div class="admin-chart-track">
<div class="admin-chart-fill accent-blue" style="width:@BarWidth(point.Total, trendMax)"></div>
</div>
<span>@point.Total</span>
</div>
}
</div>
</div>
}
</AdminDataPanel>
</MudItem>
<MudItem xs="12" md="6">
<AdminDataPanel>
<AdminPageHeader Title="바로가기" Subtitle="운영 중 자주 사용하는 화면으로 이동합니다." />
<div class="admin-quick-grid">
@foreach (var action in QuickActions)
{
<MudPaper Class="admin-quick-card" Elevation="0" @onclick="@(() => Navigation.NavigateTo(action.Href))">
<MudIcon Icon="@action.Icon" />
<span>@action.Label</span>
</MudPaper>
}
</div>
</AdminDataPanel>
</MudItem>
<MudItem xs="12" md="6">
<AdminDataPanel>
<AdminPageHeader Title="운영 메모" Subtitle="이번 배포에서 확인해야 할 핵심 포인트입니다." />
<div class="admin-note-list"> <div class="admin-note-list">
<div>대시보드와 메뉴가 역할 기반으로 필터링됩니다.</div> <div>대시보드 본문을 단계적으로 복원하며 `get` interop 오류를 추적합니다.</div>
<div>공통 CRUD 화면은 `AdminPageHeader`/`AdminCrudPageShell` 슬롯 패턴을 따릅니다.</div> <div>현재 화면은 JS 호출 없이 렌더만 수행합니다.</div>
<div>월간 통계는 문의 전환 흐름을 빠르게 보는 용도입니다.</div>
</div> </div>
</AdminDataPanel> </AdminDataPanel>
</MudItem>
<MudItem xs="12" md="6">
<AdminDataPanel>
<AdminPageHeader Title="알림" Subtitle="운영 상태를 빠르게 점검합니다." />
<MudStack Spacing="1">
<MudAlert Severity="@(summary is not null && summary.NewInquiries > 0 ? Severity.Warning : Severity.Info)">
@(summary is not null && summary.NewInquiries > 0
? $"처리 대기 중인 신규 문의가 {summary.NewInquiries}건 있습니다."
: "현재 신규 문의가 없습니다.")
</MudAlert>
<MudAlert Severity="@(summary is not null && summary.PublishedPosts < summary.TotalPosts ? Severity.Info : Severity.Success)">
@(summary is not null && summary.PublishedPosts < summary.TotalPosts
? "비공개 중인 게시글이 있어 발행 상태 확인이 필요합니다."
: "게시글 발행 상태가 안정적입니다.")
</MudAlert>
</MudStack>
</AdminDataPanel>
</MudItem>
}
</MudGrid>
@code {
private bool loading = true;
private bool statsLoading = true;
private AdminDashboardSummary? summary;
private TaxBaik.Web.Client.Components.Admin.Services.MonthlyStatsDto? monthlyStats;
private readonly List<(string Label, int Total)> trendPoints = [];
private static readonly (string Label, string Href, string Icon)[] QuickActions =
[
("공지 등록", "/taxbaik/admin/announcements/create", Icons.Material.Filled.Campaign),
("고객사 등록", "/taxbaik/admin/companies/create", Icons.Material.Filled.Business),
("문의 관리", "/taxbaik/admin/inquiries", Icons.Material.Filled.Inbox),
("설정 열기", "/taxbaik/admin/settings", Icons.Material.Filled.Settings),
];
protected override async Task OnInitializedAsync()
{
await LoadAsync();
await LoadStatsAsync();
await LoadTrendAsync();
}
private async Task ReloadAsync()
{
await LoadAsync();
}
private async Task LoadAsync()
{
loading = true;
try
{
summary = await DashboardClient.GetSummaryAsync();
}
finally
{
loading = false;
}
}
private async Task LoadStatsAsync()
{
statsLoading = true;
try
{
monthlyStats = await DashboardClient.GetMonthlyStatsAsync();
}
finally
{
statsLoading = false;
}
}
private async Task LoadTrendAsync()
{
trendPoints.Clear();
var today = DateTime.Today;
for (var i = 5; i >= 0; i--)
{
var month = today.AddMonths(-i);
var monthly = await DashboardClient.GetMonthlyStatsAsync(month.ToString("yyyy-MM"));
trendPoints.Add(($"{month:MM}", monthly.TotalInquiries));
}
}
private static string BarWidth(int value, int total)
=> total <= 0 ? "0%" : $"{Math.Max(6, (int)Math.Round(value * 100.0 / total))}%";
private int trendMax => Math.Max(1, trendPoints.Count == 0 ? 1 : trendPoints.Max(x => x.Total));
}
@@ -9,12 +9,8 @@
<div class="admin-brand-subtitle">세무회계 관리 대시보드</div> <div class="admin-brand-subtitle">세무회계 관리 대시보드</div>
</div> </div>
<div class="admin-topbar-actions"> <div class="admin-topbar-actions">
<MudButton Variant="Variant.Text" Size="Size.Small" Class="admin-topbar-action" Href="/taxbaik" Target="_blank" Rel="noopener"> <a class="admin-topbar-action" href="/taxbaik" target="_blank" rel="noopener">공개 사이트</a>
공개 사이트 <a class="admin-topbar-action" href="/taxbaik/admin/logout">로그아웃</a>
</MudButton>
<MudButton Variant="Variant.Text" Size="Size.Small" Class="admin-topbar-action" Href="/taxbaik/admin/logout">
로그아웃
</MudButton>
</div> </div>
</header> </header>
@@ -45,6 +41,8 @@
private void OnLocationChanged(object? sender, LocationChangedEventArgs args) private void OnLocationChanged(object? sender, LocationChangedEventArgs args)
{ {
drawerOpen = true;
InvokeAsync(StateHasChanged);
} }
private void ToggleDrawer() private void ToggleDrawer()
@@ -11,36 +11,26 @@
</div> </div>
<nav class="admin-nav" aria-label="관리자 메뉴"> <nav class="admin-nav" aria-label="관리자 메뉴">
<MudNavMenu Dense="true">
@foreach (var section in visibleSections) @foreach (var section in visibleSections)
{ {
<MudText Typo="Typo.caption" Class="admin-nav-group">@section.Label</MudText> <div class="admin-nav-group">@section.Label</div>
@foreach (var item in section.Items) @foreach (var item in section.Items)
{ {
<MudNavLink Href="@item.Href" Match="@(item.IsExact ? NavLinkMatch.All : NavLinkMatch.Prefix)"> <a class="admin-nav-link" href="@item.Href">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1"> <span class="admin-nav-link-icon">@item.Icon</span>
<MudIcon Icon="@item.Icon" Size="Size.Small" /> <span class="admin-nav-link-label">@item.Label</span>
<span>@item.Label</span>
@if (!string.IsNullOrWhiteSpace(item.Badge)) @if (!string.IsNullOrWhiteSpace(item.Badge))
{ {
<MudChip T="string" Size="Size.Small" Class="admin-nav-badge">@item.Badge</MudChip> <span class="admin-nav-badge">@item.Badge</span>
} }
</MudStack> </a>
</MudNavLink>
} }
} }
</MudNavMenu>
</nav> </nav>
<div class="admin-drawer-footer"> <div class="admin-drawer-footer">
<div class="admin-footer-item"> <a class="admin-footer-item" href="/taxbaik" target="_blank" rel="noopener">공개 사이트</a>
<MudIcon Icon="@Icons.Material.Filled.OpenInNew" Size="Size.Small" /> <a class="admin-footer-item" href="/taxbaik/admin/logout">로그아웃</a>
<MudLink Href="/taxbaik" Target="_blank" Underline="Underline.None">공개 사이트</MudLink>
</div>
<div class="admin-footer-item">
<MudIcon Icon="@Icons.Material.Filled.Logout" Size="Size.Small" />
<MudLink Href="/taxbaik/admin/logout" Underline="Underline.None">로그아웃</MudLink>
</div>
</div> </div>
</aside> </aside>