P1 Bug Fixes: - Fix Finance stat spacing (0₫ → 0 ₫) - Replace 3 empty catch blocks with error state UI + logging - Make POS button functional (navigate to /pos) - Add @implements IDisposable to AdminLayout P2 Code Quality: - Create ShopVerticalHelper.cs (DRY icon/label/normalize) - Remove dead overview case from ShopPage.razor - Delete 15 orphaned admin pages (moved to shop-scope) - ShopSidebarConfig delegates to ShopVerticalHelper P3 UX Enhancement: - Add CTA buttons to all empty states - Real KPIs in ShopOverview (orders, products, revenue) - User role from auth token (Admin/Khách) - Responsive CSS for stat cards, tables, mobile sidebar
223 lines
11 KiB
Plaintext
223 lines
11 KiB
Plaintext
@page "/admin"
|
|
@layout AdminLayout
|
|
@inherits AdminBase
|
|
@inject PosDataService DataService
|
|
@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">Dashboard</h1>
|
|
<p class="admin-topbar__subtitle">Tổng quan kinh doanh • @GetTodayFormatted()</p>
|
|
</div>
|
|
<div class="admin-topbar__right">
|
|
<div class="admin-search">
|
|
<i data-lucide="search"></i>
|
|
<input type="text" placeholder="Tìm kiếm..." @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>Tạo cửa hàng</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">Cửa hàng</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">Đang hoạt động</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">Đang thiết lập</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">Ngành hàng</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">Quản lý tất cả →</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;">Welcome! Tạo cửa hàng đầu tiên</h3>
|
|
<p style="font-size:14px;color:var(--pos-text-tertiary, #ADADB0);margin:0 0 20px;">Bắt đầu bằng việc tạo cửa hàng để quản lý kinh doanh của bạn.</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>
|
|
Tạo cửa hàng ngay
|
|
</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" ? "Đang mở" : "Thiết lập")
|
|
</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>
|
|
Thao tác nhanh
|
|
</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>Tạo cửa hàng mới</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>Cài đặt hệ thống</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>Phân quyền</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>
|
|
Trạng thái hệ thống
|
|
</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()
|
|
{
|
|
IsLoading = true;
|
|
try
|
|
{
|
|
_shops = await DataService.GetShopsAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_shops = new();
|
|
Console.Error.WriteLine($"[Dashboard] Error loading shops: {ex}");
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
}
|