- Inject PosDataService and load shops in OnInitializedAsync - Show 'Welcome! Tạo cửa hàng đầu tiên' with onboarding link when no shops - Render dynamic shop cards from DB data when shops exist - Keep existing KPI cards and alerts/activity panels unchanged Co-authored-by: Velik <hongochai10@users.noreply.github.com>
276 lines
13 KiB
Plaintext
276 lines
13 KiB
Plaintext
@page "/admin"
|
|
@layout AdminLayout
|
|
@inherits AdminBase
|
|
@inject PosDataService DataService
|
|
@using WebClientTpos.Client.Services
|
|
|
|
@*
|
|
EN: Admin Dashboard — overview of business metrics, stores, alerts, and recent activity.
|
|
VI: Dashboard Admin — tổng quan chỉ số kinh doanh, cửa hàng, cảnh báo, hoạt động gần đây.
|
|
Design: pencil-design/src/pages/tPOS/admin/admin-dashboard.pen
|
|
*@
|
|
|
|
<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>
|
|
<span class="admin-icon-btn__dot"></span>
|
|
</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 ── *@
|
|
<div class="admin-kpi-row">
|
|
@* KPI 1: Tổng doanh thu *@
|
|
<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="trending-up" style="color:#22C55E;"></i>
|
|
</div>
|
|
<div class="admin-kpi-card__badge admin-kpi-card__badge--up">
|
|
<i data-lucide="arrow-up"></i>
|
|
<span>+18.2%</span>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">128.5M</div>
|
|
<div class="admin-kpi-card__label">Tổng doanh thu</div>
|
|
</div>
|
|
|
|
@* KPI 2: Tổng đơn hàng *@
|
|
<div class="admin-kpi-card">
|
|
<div class="admin-kpi-card__header">
|
|
<div class="admin-kpi-card__icon" style="background-color:rgba(59,130,246,0.125);">
|
|
<i data-lucide="shopping-bag" style="color:#3B82F6;"></i>
|
|
</div>
|
|
<div class="admin-kpi-card__badge admin-kpi-card__badge--up">
|
|
<i data-lucide="arrow-up"></i>
|
|
<span>+12.4%</span>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">1,247</div>
|
|
<div class="admin-kpi-card__label">Tổng đơn hàng</div>
|
|
</div>
|
|
|
|
@* KPI 3: Cửa hàng hoạt độ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 class="admin-kpi-card__badge admin-kpi-card__badge--up">
|
|
<span>3 online</span>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">3</div>
|
|
<div class="admin-kpi-card__label">Cửa hàng hoạt động</div>
|
|
</div>
|
|
|
|
@* KPI 4: Nhân viên online *@
|
|
<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="users" style="color:#EC4899;"></i>
|
|
</div>
|
|
<div class="admin-kpi-card__badge admin-kpi-card__badge--up">
|
|
<i data-lucide="arrow-up"></i>
|
|
<span>+22.7%</span>
|
|
</div>
|
|
</div>
|
|
<div class="admin-kpi-card__value">12</div>
|
|
<div class="admin-kpi-card__label">Nhân viên online</div>
|
|
</div>
|
|
</div>
|
|
|
|
@* ── BOTTOM ROW: Store Overview + Right Column ── *@
|
|
<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/onboarding/store" 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)
|
|
{
|
|
<div class="admin-store-card">
|
|
<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="@GetShopIcon(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">@(shop.Category ?? "Shop") • @(shop.Description ?? 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>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@* ── RIGHT COLUMN: Alerts + Activity ── *@
|
|
<div style="width:380px;display:flex;flex-direction:column;gap:20px;">
|
|
@* Alerts Panel *@
|
|
<div class="admin-panel">
|
|
<div class="admin-panel__header">
|
|
<h3 class="admin-panel__title">
|
|
<i data-lucide="alert-triangle" style="color:#F59E0B;"></i>
|
|
Cảnh báo
|
|
<span class="admin-badge-count admin-badge-count--danger">4</span>
|
|
</h3>
|
|
</div>
|
|
<div class="admin-panel__body">
|
|
<div class="admin-alert-list">
|
|
<div class="admin-alert-item admin-alert-item--danger">
|
|
<i data-lucide="package-x" style="color:#EF4444;"></i>
|
|
<div class="admin-alert-item__text">
|
|
<div class="admin-alert-item__title">5 sản phẩm sắp hết hàng</div>
|
|
<div class="admin-alert-item__sub">Coffee House Q1</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-alert-item admin-alert-item--warning">
|
|
<i data-lucide="clock" style="color:#F59E0B;"></i>
|
|
<div class="admin-alert-item__text">
|
|
<div class="admin-alert-item__title">Ca tối thiếu 1 nhân viên</div>
|
|
<div class="admin-alert-item__sub">Nhà hàng Q3 • Ngày mai</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-alert-item admin-alert-item--info">
|
|
<i data-lucide="printer" style="color:#3B82F6;"></i>
|
|
<div class="admin-alert-item__text">
|
|
<div class="admin-alert-item__title">Máy in mất kết nối</div>
|
|
<div class="admin-alert-item__sub">Coffee House Q1 • Kitchen</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@* Recent Activity 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>
|
|
Hoạt động gần đây
|
|
</h3>
|
|
</div>
|
|
<div class="admin-panel__body">
|
|
<div class="admin-activity-list">
|
|
<div class="admin-activity-item">
|
|
<span class="admin-activity-dot" style="background-color:#22C55E;"></span>
|
|
<div class="admin-activity-item__text">
|
|
<div class="admin-activity-item__title">Đơn #2847 hoàn thành</div>
|
|
<div class="admin-activity-item__time">Coffee House Q1 • 2 phút trước</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-activity-item">
|
|
<span class="admin-activity-dot" style="background-color:#3B82F6;"></span>
|
|
<div class="admin-activity-item__text">
|
|
<div class="admin-activity-item__title">Nguyễn Văn A clock-in</div>
|
|
<div class="admin-activity-item__time">Nhà hàng Q3 • 5 phút trước</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-activity-item">
|
|
<span class="admin-activity-dot" style="background-color:#F59E0B;"></span>
|
|
<div class="admin-activity-item__text">
|
|
<div class="admin-activity-item__title">Nhập kho 15 sản phẩm</div>
|
|
<div class="admin-activity-item__time">Coffee House Q1 • 12 phút trước</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-activity-item">
|
|
<span class="admin-activity-dot" style="background-color:#8B5CF6;"></span>
|
|
<div class="admin-activity-item__text">
|
|
<div class="admin-activity-item__title">Cập nhật menu buổi tối</div>
|
|
<div class="admin-activity-item__time">Nhà hàng Q3 • 28 phút trước</div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-activity-item">
|
|
<span class="admin-activity-dot" style="background-color:#EC4899;"></span>
|
|
<div class="admin-activity-item__text">
|
|
<div class="admin-activity-item__title">Khách VIP mới: Trần Thị B</div>
|
|
<div class="admin-activity-item__time">Hệ thống • 45 phút trước</div>
|
|
</div>
|
|
</div>
|
|
</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
|
|
{
|
|
_shops = new();
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
private static string GetShopIcon(string? category) => category?.ToLowerInvariant() switch
|
|
{
|
|
"cafe" or "café" or "coffee" => "coffee",
|
|
"restaurant" or "nhà hàng" => "utensils",
|
|
"karaoke" => "mic",
|
|
"spa" => "sparkles",
|
|
"retail" => "shopping-bag",
|
|
_ => "store"
|
|
};
|
|
}
|