Files
pos-system/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Customer/CustomerDatabase.razor

138 lines
6.8 KiB
Plaintext

@page "/admin/customers"
@layout AdminLayout
@inherits AdminBase
@inject PosDataService DataService
@using WebClientTpos.Client.Services
@*
EN: Customer database — real data from membership_service via BFF.
VI: Cơ sở dữ liệu khách hàng — dữ liệu thực từ membership_service qua BFF.
*@
<PageTitle>Khách hàng — GoodGo Admin</PageTitle>
<div class="admin-topbar">
<div class="admin-topbar__left">
<h1 class="admin-topbar__title">Khách hàng</h1>
<p class="admin-topbar__subtitle">@_members.Count thành viên</p>
</div>
<div class="admin-topbar__right">
<div class="admin-search" style="width:220px;">
<i data-lucide="search"></i>
<input type="text" placeholder="Tìm khách hàng..." @bind="_searchQuery" @bind:event="oninput" />
</div>
</div>
</div>
@* ═══ SUMMARY ═══ *@
<div class="admin-content" style="display:flex;flex-direction:column;gap:20px;">
<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(139,92,246,0.1);">
<i data-lucide="users" style="color:#8B5CF6;"></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 thành viên</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="crown" style="color:#FF5C00;"></i>
</div>
<div class="admin-stat-card__content">
<span class="admin-stat-card__value">@_members.Count(m => m.CurrentLevel >= 3)</span>
<span class="admin-stat-card__label">VIP</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">@_members.Where(m => m.CreatedAt >= DateTime.UtcNow.AddDays(-30)).Count()</span>
<span class="admin-stat-card__label">Mới (30 ngày)</span>
</div>
</div>
</div>
@* ═══ MEMBERS TABLE ═══ *@
@if (IsLoading)
{
<div style="text-align:center;padding:48px;">
<div class="spinner-small" style="width:32px;height:32px;margin:0 auto 16px;"></div>
<p style="color:var(--admin-text-tertiary);font-size:14px;">Đang tải dữ liệu...</p>
</div>
}
else if (!_members.Any())
{
<div style="text-align:center;padding:60px 20px;">
<div style="width:80px;height:80px;border-radius:24px;background:rgba(139,92,246,0.1);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
<i data-lucide="users" style="width:36px;height:36px;color:#8B5CF6;"></i>
</div>
<h2 style="font-size:20px;font-weight:700;margin:0 0 8px;color:var(--pos-text-primary, #FFFFFF);">Chưa có thành viên</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Khách hàng sẽ tự động trở thành thành viên khi mua hàng</p>
</div>
}
else
{
<div class="admin-panel">
<div class="admin-panel__body" style="padding:0;">
<table class="admin-table" style="width:100%;">
<thead>
<tr>
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">ID</th>
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Cấp bậc</th>
<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);">Giới tính</th>
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Quốc gia</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)
{
<tr style="border-top:1px solid var(--admin-border-subtle);">
<td style="padding:12px 16px;font-size:12px;font-family:monospace;color:var(--admin-text-tertiary);">@m.Id.ToString()[..8]...</td>
<td style="padding:12px 16px;">
<span style="display:inline-flex;align-items:center;gap:6px;">
<span style="width:8px;height:8px;border-radius:50%;background:@GetLevelColor(m.CurrentLevel);"></span>
<span style="font-weight:600;font-size:14px;">@(m.LevelName ?? $"Level {m.CurrentLevel}")</span>
</span>
</td>
<td style="padding:12px 16px;text-align:right;font-size:14px;font-weight:600;color:var(--admin-orange-primary);">@m.TotalExpEarned.ToString("N0")</td>
<td style="padding:12px 16px;font-size:14px;">@(m.Gender ?? "—")</td>
<td style="padding:12px 16px;font-size:14px;">@(m.CountryCode ?? "—")</td>
<td style="padding:12px 16px;font-size:13px;color:var(--admin-text-tertiary);">@m.CreatedAt.ToString("dd/MM/yyyy")</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
</div>
@code {
private string _searchQuery = "";
private List<PosDataService.MemberInfo> _members = new();
protected override async Task OnInitializedAsync()
{
IsLoading = true;
try { _members = await DataService.GetMembersAsync(); }
catch { }
finally { IsLoading = false; }
}
private static string GetLevelColor(int level) => level switch
{
1 => "#94A3B8",
2 => "#22C55E",
3 => "#3B82F6",
4 => "#F59E0B",
5 => "#FF5C00",
_ => "#8B5CF6"
};
}