feat(admin): enhance Customer search + Finance date filter (G6, G8)
- Customers: add search bar (filter by ID, membership level) - Customers: membership level displays as badge - Customers: show count header with search input - Finance: add date range tabs (7 ngày / 30 ngày / Tất cả) - Finance: revenue/order stats update based on selected period
This commit is contained in:
@@ -214,10 +214,28 @@
|
||||
|
||||
// ═══ FINANCE ═══
|
||||
case "finance":
|
||||
var finOrders = _financePeriod switch {
|
||||
"7d" => _orders.Where(o => o.CreatedAt >= DateTime.UtcNow.AddDays(-7)).ToList(),
|
||||
"30d" => _orders.Where(o => o.CreatedAt >= DateTime.UtcNow.AddDays(-30)).ToList(),
|
||||
_ => _orders };
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
||||
<h3 style="margin:0;font-size:16px;font-weight:700;">Tài chính</h3>
|
||||
<div style="display:flex;gap:4px;background:var(--admin-bg-elevated);border-radius:8px;padding:3px;">
|
||||
@foreach (var (label, val) in new[] { ("7 ngày", "7d"), ("30 ngày", "30d"), ("Tất cả", "all") })
|
||||
{
|
||||
<button @onclick="@(() => { _financePeriod = val; StateHasChanged(); })"
|
||||
style="padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;border:none;
|
||||
background:@(_financePeriod == val ? "var(--admin-orange-primary)" : "transparent");
|
||||
color:@(_financePeriod == val ? "#FFF" : "var(--admin-text-tertiary)");">
|
||||
@label
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;">
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(34,197,94,0.1);"><i data-lucide="trending-up" style="color:#22C55E;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@FormatVND(_orders.Sum(o => o.TotalAmount))</span><span class="admin-stat-card__label">Tổng doanh thu</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(59,130,246,0.1);"><i data-lucide="receipt" style="color:#3B82F6;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_orders.Count</span><span class="admin-stat-card__label">Đơn hàng</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(255,92,0,0.1);"><i data-lucide="calculator" style="color:#FF5C00;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@FormatVND(_orders.Any() ? _orders.Average(o => o.TotalAmount) : 0)</span><span class="admin-stat-card__label">TB/đơn</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(34,197,94,0.1);"><i data-lucide="trending-up" style="color:#22C55E;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@FormatVND(finOrders.Sum(o => o.TotalAmount))</span><span class="admin-stat-card__label">Tổng doanh thu</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(59,130,246,0.1);"><i data-lucide="receipt" style="color:#3B82F6;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@finOrders.Count</span><span class="admin-stat-card__label">Đơn hàng</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(255,92,0,0.1);"><i data-lucide="calculator" style="color:#FF5C00;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@FormatVND(finOrders.Any() ? finOrders.Average(o => o.TotalAmount) : 0)</span><span class="admin-stat-card__label">TB/đơn</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(139,92,246,0.1);"><i data-lucide="wallet" style="color:#8B5CF6;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@FormatVND(_wallets.Sum(w => w.Balance))</span><span class="admin-stat-card__label">Số dư ví</span></div></div>
|
||||
</div>
|
||||
@if (_walletTxns.Any())
|
||||
@@ -346,16 +364,28 @@
|
||||
|
||||
// ═══ CUSTOMERS + MEMBERSHIP LEVELS ═══
|
||||
case "customers":
|
||||
@if (!_members.Any())
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
||||
<h3 style="margin:0;font-size:16px;font-weight:700;">@_members.Count khách hàng</h3>
|
||||
<div style="position:relative;">
|
||||
<i data-lucide="search" style="position:absolute;left:12px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--admin-text-tertiary);"></i>
|
||||
<input type="text" placeholder="Tìm theo ID..." @bind="_customerSearch" @bind:event="oninput"
|
||||
style="padding:8px 12px 8px 36px;border-radius:8px;border:1px solid var(--admin-border-subtle);background:var(--admin-bg-elevated);font-size:13px;color:var(--admin-text-primary);width:200px;" />
|
||||
</div>
|
||||
</div>
|
||||
var filteredMembers = string.IsNullOrWhiteSpace(_customerSearch)
|
||||
? _members
|
||||
: _members.Where(m => m.Id.ToString().Contains(_customerSearch, StringComparison.OrdinalIgnoreCase)
|
||||
|| (m.LevelName ?? "").Contains(_customerSearch, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
@if (!filteredMembers.Any())
|
||||
{
|
||||
@RenderEmpty("heart", "#EF4444", "Chưa có khách hàng", "Khách hàng sẽ hiển thị khi có giao dịch", "monitor", "Mở POS bán hàng", $"/pos/{ShopId}/{_posVertical}")
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;">
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(255,92,0,0.1);"><i data-lucide="users" style="color:#FF5C00;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_members.Count</span><span class="admin-stat-card__label">Tổng khách hàng</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(255,92,0,0.1);"><i data-lucide="users" style="color:#FF5C00;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@filteredMembers.Count</span><span class="admin-stat-card__label">Tổng khách hàng</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(139,92,246,0.1);"><i data-lucide="crown" style="color:#8B5CF6;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_memberLevels.Count</span><span class="admin-stat-card__label">Cấp bậc</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(34,197,94,0.1);"><i data-lucide="star" style="color:#22C55E;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@(_members.Any() ? _members.Max(m => m.TotalExpEarned).ToString("N0") : "0")</span><span class="admin-stat-card__label">EXP cao nhất</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(34,197,94,0.1);"><i data-lucide="star" style="color:#22C55E;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@(filteredMembers.Any() ? filteredMembers.Max(m => m.TotalExpEarned).ToString("N0") : "0")</span><span class="admin-stat-card__label">EXP cao nhất</span></div></div>
|
||||
</div>
|
||||
@if (_memberLevels.Any())
|
||||
{
|
||||
@@ -390,11 +420,11 @@
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">EXP</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Ngày tham gia</th>
|
||||
</tr></thead><tbody>
|
||||
@foreach (var m in _members)
|
||||
@foreach (var m in filteredMembers)
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:600;font-family:monospace;font-size:12px;">@m.Id.ToString()[..8]</td>
|
||||
<td style="padding:12px 16px;">@(m.LevelName ?? "—")</td>
|
||||
<td style="padding:12px 16px;"><span class="admin-status-badge admin-status-badge--online" style="font-size:11px;padding:2px 10px;">@(m.LevelName ?? "—")</span></td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:var(--admin-orange-primary);">@m.TotalExpEarned.ToString("N0")</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--admin-text-tertiary);">@m.CreatedAt.ToString("dd/MM/yyyy")</td>
|
||||
</tr>
|
||||
@@ -889,6 +919,10 @@
|
||||
private List<PosDataService.ScheduleInfo> _staffSchedules = new();
|
||||
private List<PosDataService.InventoryTxnInfo> _invTxns = new();
|
||||
private List<PosDataService.ResourceInfo> _resources = new();
|
||||
// Customer filter state
|
||||
private string _customerSearch = "";
|
||||
// Finance date range filter state
|
||||
private string _financePeriod = "all"; // 7d, 30d, all
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user