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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user