fix(overview): load all orders and add date range selector to shop overview

- Change orders fetch from "today" filter to "all" so KPIs show actual data
- Add date range presets (Hôm nay / 7 ngày / 30 ngày / Tất cả)
- Add weekly period tab to revenue chart
- Display filtered recent orders based on selected period

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-25 15:27:46 +07:00
parent af1b1fb101
commit 6a9aa0d46f

View File

@@ -89,6 +89,22 @@
</div>
</div>
@* ── Date Range Selector ── *@
<div style="display:flex;align-items:center;gap:8px;">
<div style="display:flex;gap:4px;background:var(--admin-bg-elevated);border-radius:8px;padding:3px;">
@foreach (var (label, days) in new[] { ("Hôm nay", 0), ("7 ngày", 7), ("30 ngày", 30), ("Tất cả", -1) })
{
<button @onclick="@(() => SetPeriod(days))"
style="padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;border:none;
background:@(_activePeriod == days ? "var(--admin-orange-primary)" : "transparent");
color:@(_activePeriod == days ? "#FFF" : "var(--admin-text-tertiary)");">
@label
</button>
}
</div>
<span style="font-size:12px;color:var(--admin-text-tertiary);">@GetPeriodLabel()</span>
</div>
@* ── KPI ROW ── *@
<div class="admin-kpi-row">
<div class="admin-kpi-card">
@@ -97,8 +113,8 @@
<i data-lucide="trending-up" style="color:#22C55E;"></i>
</div>
</div>
<div class="admin-kpi-card__value">@FormatVND(_orders.Sum(o => o.TotalAmount))</div>
<div class="admin-kpi-card__label">Doanh thu tháng</div>
<div class="admin-kpi-card__value">@FormatVND(GetFilteredOrders().Sum(o => o.TotalAmount))</div>
<div class="admin-kpi-card__label">Doanh thu</div>
</div>
<div class="admin-kpi-card">
<div class="admin-kpi-card__header">
@@ -106,8 +122,8 @@
<i data-lucide="shopping-bag" style="color:#3B82F6;"></i>
</div>
</div>
<div class="admin-kpi-card__value">@_orders.Count</div>
<div class="admin-kpi-card__label">Đơn hàng tháng</div>
<div class="admin-kpi-card__value">@GetFilteredOrders().Count</div>
<div class="admin-kpi-card__label">Đơn hàng</div>
</div>
<div class="admin-kpi-card">
<div class="admin-kpi-card__header">
@@ -115,7 +131,7 @@
<i data-lucide="banknote" style="color:#8B5CF6;"></i>
</div>
</div>
<div class="admin-kpi-card__value">@FormatVND(_orders.Any() ? _orders.Average(o => o.TotalAmount) : 0)</div>
<div class="admin-kpi-card__value">@FormatVND(GetFilteredOrders().Any() ? GetFilteredOrders().Average(o => o.TotalAmount) : 0)</div>
<div class="admin-kpi-card__label">Giá trị TB / đơn</div>
</div>
<div class="admin-kpi-card">
@@ -136,26 +152,36 @@
<div class="admin-panel__header">
<h3 class="admin-panel__title">
<i data-lucide="bar-chart-2" style="color:var(--admin-orange-primary);"></i>
Doanh thu 7 ngày gần nhất
Biểu đồ doanh thu
</h3>
<div style="display:flex;gap:8px;">
<button class="admin-tab @(_revenuePeriod == "daily" ? "admin-tab--active" : "")" style="padding:6px 12px;font-size:12px;" @onclick="@(() => SwitchRevenuePeriod("daily"))">7 ngày</button>
<button class="admin-tab @(_revenuePeriod == "monthly" ? "admin-tab--active" : "")" style="padding:6px 12px;font-size:12px;" @onclick="@(() => SwitchRevenuePeriod("monthly"))">30 ngày</button>
<button class="admin-tab @(_revenuePeriod == "daily" ? "admin-tab--active" : "")" style="padding:6px 12px;font-size:12px;" @onclick="@(() => SwitchRevenuePeriod("daily"))">Ngày</button>
<button class="admin-tab @(_revenuePeriod == "weekly" ? "admin-tab--active" : "")" style="padding:6px 12px;font-size:12px;" @onclick="@(() => SwitchRevenuePeriod("weekly"))">Tuần</button>
<button class="admin-tab @(_revenuePeriod == "monthly" ? "admin-tab--active" : "")" style="padding:6px 12px;font-size:12px;" @onclick="@(() => SwitchRevenuePeriod("monthly"))">Tháng</button>
</div>
</div>
<div class="admin-panel__body" style="min-height:200px;">
@if (_dailyRevenue.Any())
{
var recent = _revenuePeriod == "monthly" ? _dailyRevenue.TakeLast(30).ToList() : _dailyRevenue.TakeLast(7).ToList();
var recent = _revenuePeriod switch {
"monthly" => _dailyRevenue.TakeLast(12).ToList(),
"weekly" => _dailyRevenue.TakeLast(8).ToList(),
_ => _dailyRevenue.TakeLast(7).ToList()
};
var maxVal = recent.Max(r => r.Revenue);
<div style="display:flex;align-items:flex-end;gap:8px;height:200px;padding:0 8px;">
@foreach (var r in recent)
{
var pct = maxVal > 0 ? (int)(r.Revenue / maxVal * 100) : 0;
var barLabel = _revenuePeriod switch {
"monthly" => r.PeriodStart.ToString("MM/yy"),
"weekly" => $"T{r.PeriodStart:dd/MM}",
_ => r.PeriodStart.ToString("dd/MM")
};
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0;">
<span style="font-size:10px;font-weight:600;color:var(--admin-orange-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;">@FormatVND(r.Revenue)</span>
<div style="width:100%;height:@(Math.Max(pct, 4))%;min-height:4px;background:var(--admin-orange-primary);border-radius:6px 6px 0 0;transition:height 0.3s;"></div>
<span style="font-size:10px;color:var(--admin-text-tertiary);">@r.PeriodStart.ToString("dd/MM")</span>
<span style="font-size:10px;color:var(--admin-text-tertiary);">@barLabel</span>
</div>
}
</div>
@@ -183,10 +209,10 @@
</h3>
</div>
<div class="admin-panel__body" style="padding:0;">
@if (_orders.Any())
@if (GetFilteredOrders().Any())
{
<div style="display:flex;flex-direction:column;">
@foreach (var o in _orders.Take(5))
@foreach (var o in GetFilteredOrders().Take(5))
{
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 16px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="display:flex;flex-direction:column;gap:2px;">
@@ -244,13 +270,12 @@
private PosDataService.ShopInfo? _shop;
private string _posVertical = "cafe";
private List<PosDataService.OrderInfo> _orders = new();
private List<PosDataService.OrderInfo> _allOrders = new();
private List<PosDataService.AdminProductInfo> _products = new();
private List<PosDataService.RevenueReportItem> _dailyRevenue = new();
private string _revenuePeriod = "daily";
private int _activePeriod = 30; // default 30 days
// EN: Cascade layout reference to set shop context for sidebar switching.
// VI: Cascade layout để set shop context cho sidebar chuyển đổi.
[CascadingParameter] public AdminLayout? Layout { get; set; }
protected override async Task OnInitializedAsync()
@@ -267,11 +292,12 @@
_posVertical = MapCategoryToVertical(_shop.Category);
Layout?.SetShopContext(ShopId, _shop.Name ?? "Cửa hàng", _shop.Category);
}
// EN: Load KPI data in parallel / VI: Tải dữ liệu KPI song song
var ordersTask = DataService.GetOrdersAsync(id);
// EN: Load all orders + products + revenue in parallel
// VI: Tải tất cả đơn hàng + sản phẩm + doanh thu song song
var ordersTask = DataService.GetOrdersAsync(id, "all");
var productsTask = DataService.GetAllProductsAsync(id);
var revenueTask = DataService.GetRevenueReportAsync("daily", id);
_orders = await ordersTask;
_allOrders = await ordersTask;
_products = await productsTask;
try { _dailyRevenue = await revenueTask; } catch { _dailyRevenue = new(); }
}
@@ -284,6 +310,26 @@
finally { IsLoading = false; }
}
private List<PosDataService.OrderInfo> GetFilteredOrders()
{
if (_activePeriod == -1) return _allOrders; // all
if (_activePeriod == 0) return _allOrders.Where(o => o.CreatedAt.Date == DateTime.UtcNow.Date).ToList(); // today
return _allOrders.Where(o => o.CreatedAt >= DateTime.UtcNow.AddDays(-_activePeriod)).ToList();
}
private void SetPeriod(int days)
{
_activePeriod = days;
StateHasChanged();
}
private string GetPeriodLabel() => _activePeriod switch
{
0 => DateTime.UtcNow.ToString("dd/MM/yyyy"),
-1 => "Toàn bộ thời gian",
_ => $"{DateTime.UtcNow.AddDays(-_activePeriod):dd/MM} — {DateTime.UtcNow:dd/MM/yyyy}"
};
private async Task SwitchRevenuePeriod(string period)
{
_revenuePeriod = period;
@@ -295,9 +341,7 @@
}
private static string FormatVND(decimal val) => val.ToString("N0") + " ₫";
private static string GetStatusBadgeClass(string? status) => ShopVerticalHelper.GetStatusBadgeClass(status);
private static string GetStatusLabel(string? status) => ShopVerticalHelper.GetStatusLabel(status);
private static string MapCategoryToVertical(string? category) => (category?.ToLowerInvariant()) switch