Root cause: AdminBase had no auth initialization. AuthStateService is in-memory Singleton — token is null after page refresh. Admin pages called PosDataService without token → BFF forwarded requests without Authorization header → microservices returned 401. Fix: - AdminBase.cs: inject AuthService, call TryRestoreSessionAsync() - 9 admin pages: add await base.OnInitializedAsync() calls - BffHttpClient.cs: add debug logging for auth forwarding
225 lines
11 KiB
Plaintext
225 lines
11 KiB
Plaintext
@page "/admin"
|
|
@layout AdminLayout
|
|
@inherits AdminBase
|
|
@inject PosDataService DataService
|
|
@inject Microsoft.Extensions.Localization.IStringLocalizer<Dashboard> L
|
|
@using WebClientTpos.Client.Services
|
|
|
|
@*
|
|
EN: Admin Dashboard — overview with real data from shops list.
|
|
VI: Dashboard Admin — tổng quan với dữ liệu thực từ danh sách shops.
|
|
*@
|
|
|
|
<PageTitle>Dashboard — GoodGo Admin</PageTitle>
|
|
|
|
@* ═══ TOP BAR ═══ *@
|
|
<div class="admin-topbar">
|
|
<div class="admin-topbar__left">
|
|
<h1 class="admin-topbar__title">@L["Dashboard_Title"]</h1>
|
|
<p class="admin-topbar__subtitle">@L["Dashboard_Subtitle"] • @GetTodayFormatted()</p>
|
|
</div>
|
|
<div class="admin-topbar__right">
|
|
<div class="admin-search">
|
|
<i data-lucide="search"></i>
|
|
<input type="text" placeholder="@L["Dashboard_Search"]" @bind="SearchQuery" />
|
|
</div>
|
|
<button class="admin-icon-btn" title="Thông báo">
|
|
<i data-lucide="bell"></i>
|
|
</button>
|
|
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("stores/create"))">
|
|
<i data-lucide="plus"></i>
|
|
<span>@L["Dashboard_CreateStore"]</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@* ═══ CONTENT ═══ *@
|
|
<div class="admin-content" style="display:flex;flex-direction:column;gap:24px;">
|
|
|
|
@* ── KPI ROW (data-driven) ── *@
|
|
<div class="admin-kpi-row">
|
|
@* KPI 1: Cửa hàng *@
|
|
<div class="admin-kpi-card">
|
|
<div class="admin-kpi-card__header">
|
|
<div class="admin-kpi-card__icon" style="background-color:rgba(139,92,246,0.125);">
|
|
<i data-lucide="store" style="color:#8B5CF6;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">@_shops.Count</div>
|
|
<div class="admin-kpi-card__label">@L["Dashboard_KPI_Stores"]</div>
|
|
</div>
|
|
|
|
@* KPI 2: Đang hoạt động *@
|
|
<div class="admin-kpi-card">
|
|
<div class="admin-kpi-card__header">
|
|
<div class="admin-kpi-card__icon" style="background-color:rgba(34,197,94,0.125);">
|
|
<i data-lucide="check-circle" style="color:#22C55E;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">@_shops.Count(s => s.Status == "active")</div>
|
|
<div class="admin-kpi-card__label">@L["Dashboard_KPI_Active"]</div>
|
|
</div>
|
|
|
|
@* KPI 3: Đang thiết lập *@
|
|
<div class="admin-kpi-card">
|
|
<div class="admin-kpi-card__header">
|
|
<div class="admin-kpi-card__icon" style="background-color:rgba(245,158,11,0.125);">
|
|
<i data-lucide="settings" style="color:#F59E0B;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">@_shops.Count(s => s.Status != "active")</div>
|
|
<div class="admin-kpi-card__label">@L["Dashboard_KPI_Setup"]</div>
|
|
</div>
|
|
|
|
@* KPI 4: Ngành hàng *@
|
|
<div class="admin-kpi-card">
|
|
<div class="admin-kpi-card__header">
|
|
<div class="admin-kpi-card__icon" style="background-color:rgba(236,72,153,0.125);">
|
|
<i data-lucide="layers" style="color:#EC4899;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">@_shops.Select(s => s.Category).Distinct().Count()</div>
|
|
<div class="admin-kpi-card__label">@L["Dashboard_KPI_Verticals"]</div>
|
|
</div>
|
|
</div>
|
|
|
|
@* ── BOTTOM ROW ── *@
|
|
<div style="display:flex;gap:24px;flex:1;min-height:0;">
|
|
|
|
@* ── LEFT: Store Overview ── *@
|
|
<div class="admin-panel" style="flex:1;">
|
|
<div class="admin-panel__header">
|
|
<h3 class="admin-panel__title">
|
|
<i data-lucide="store" style="color:var(--admin-orange-primary);"></i>
|
|
Cửa hàng của bạn
|
|
</h3>
|
|
@if (_shops.Count > 0)
|
|
{
|
|
<a href="/admin/stores" class="admin-panel__action">@L["Dashboard_ManageAll"] →</a>
|
|
}
|
|
</div>
|
|
<div class="admin-panel__body" style="display:flex;flex-direction:column;gap:12px;">
|
|
@if (_shops.Count == 0)
|
|
{
|
|
<div style="text-align:center;padding:40px 20px;">
|
|
<i data-lucide="store" style="width:48px;height:48px;color:var(--admin-orange-primary);margin-bottom:16px;"></i>
|
|
<h3 style="font-size:18px;font-weight:700;color:var(--pos-text-primary, #FFFFFF);margin:0 0 8px;">@L["Dashboard_WelcomeTitle"]</h3>
|
|
<p style="font-size:14px;color:var(--pos-text-tertiary, #ADADB0);margin:0 0 20px;">@L["Dashboard_WelcomeSubtitle"]</p>
|
|
<a href="/admin/stores/create" class="admin-btn-primary" style="display:inline-flex;align-items:center;gap:8px;">
|
|
<i data-lucide="plus" style="width:16px;height:16px;"></i>
|
|
@L["Dashboard_CreateStoreNow"]
|
|
</a>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
@foreach (var shop in _shops)
|
|
{
|
|
<a href="/admin/shop/@shop.Id/overview" class="admin-store-card" style="text-decoration:none;color:inherit;cursor:pointer;">
|
|
<div class="admin-store-card__top">
|
|
<div class="admin-store-card__info">
|
|
<div class="admin-store-card__avatar" style="background-color:rgba(255,92,0,0.125);">
|
|
<i data-lucide="@ShopVerticalHelper.GetIcon(shop.Category)" style="color:var(--admin-orange-primary);"></i>
|
|
</div>
|
|
<div>
|
|
<div class="admin-store-card__name">@shop.Name</div>
|
|
<div class="admin-store-card__type">@ShopSidebarConfig.GetVerticalLabel(shop.Category) • @(shop.Slug)</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-status-badge admin-status-badge--@(shop.Status == "active" ? "online" : "setup")">
|
|
<span class="admin-status-badge__dot"></span>
|
|
@(shop.Status == "active" ? L["Dashboard_Status_Open"] : L["Dashboard_Status_Setup"])
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@* ── RIGHT COLUMN ── *@
|
|
<div style="width:380px;display:flex;flex-direction:column;gap:20px;">
|
|
@* Quick Actions Panel *@
|
|
<div class="admin-panel">
|
|
<div class="admin-panel__header">
|
|
<h3 class="admin-panel__title">
|
|
<i data-lucide="zap" style="color:var(--admin-orange-primary);"></i>
|
|
@L["Dashboard_QuickActions"]
|
|
</h3>
|
|
</div>
|
|
<div class="admin-panel__body" style="display:flex;flex-direction:column;gap:8px;">
|
|
<a href="/admin/stores/create" class="admin-quick-action">
|
|
<i data-lucide="plus-circle" style="color:#22C55E;width:18px;height:18px;"></i>
|
|
<span>@L["Dashboard_QA_CreateStore"]</span>
|
|
<i data-lucide="chevron-right" style="margin-left:auto;width:16px;height:16px;color:var(--admin-text-tertiary);"></i>
|
|
</a>
|
|
<a href="/admin/system/audit" class="admin-quick-action">
|
|
<i data-lucide="settings" style="color:#3B82F6;width:18px;height:18px;"></i>
|
|
<span>@L["Dashboard_QA_SystemSettings"]</span>
|
|
<i data-lucide="chevron-right" style="margin-left:auto;width:16px;height:16px;color:var(--admin-text-tertiary);"></i>
|
|
</a>
|
|
<a href="/admin/roles" class="admin-quick-action">
|
|
<i data-lucide="shield" style="color:#8B5CF6;width:18px;height:18px;"></i>
|
|
<span>@L["Dashboard_QA_Permissions"]</span>
|
|
<i data-lucide="chevron-right" style="margin-left:auto;width:16px;height:16px;color:var(--admin-text-tertiary);"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
@* Status Panel *@
|
|
<div class="admin-panel" style="flex:1;">
|
|
<div class="admin-panel__header">
|
|
<h3 class="admin-panel__title">
|
|
<i data-lucide="activity" style="color:#22C55E;"></i>
|
|
@L["Dashboard_SystemStatus"]
|
|
</h3>
|
|
</div>
|
|
<div class="admin-panel__body" style="display:flex;flex-direction:column;gap:12px;">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<span style="color:var(--admin-text-tertiary);font-size:14px;">API Gateway</span>
|
|
<span style="font-size:13px;color:#22C55E;display:flex;align-items:center;gap:4px;">
|
|
<span style="width:6px;height:6px;border-radius:50%;background:#22C55E;display:inline-block;"></span> Online
|
|
</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<span style="color:var(--admin-text-tertiary);font-size:14px;">IAM Service</span>
|
|
<span style="font-size:13px;color:#22C55E;display:flex;align-items:center;gap:4px;">
|
|
<span style="width:6px;height:6px;border-radius:50%;background:#22C55E;display:inline-block;"></span> Online
|
|
</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<span style="color:var(--admin-text-tertiary);font-size:14px;">Merchant Service</span>
|
|
<span style="font-size:13px;color:#22C55E;display:flex;align-items:center;gap:4px;">
|
|
<span style="width:6px;height:6px;border-radius:50%;background:#22C55E;display:inline-block;"></span> Online
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private List<PosDataService.ShopInfo> _shops = new();
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await base.OnInitializedAsync();
|
|
IsLoading = true;
|
|
try
|
|
{
|
|
_shops = await DataService.GetShopsAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_shops = new();
|
|
Console.Error.WriteLine($"[Dashboard] Error loading shops: {ex}");
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
}
|