feat(multi-vertical): P1 — promotions real data, finance wallets, customer levels
- Promotions: real table from GetPromotionsAsync (stats: total, active, vouchers, used) - Finance: wallet balance + recent wallet transactions from GetWalletsAsync - Customers: membership levels table from GetMembershipLevelsAsync - Staff: schedules data wired from GetStaffSchedulesAsync - Data vars: wallets, walletTxns, promotions, memberLevels, staffSchedules, invTxns
This commit is contained in:
@@ -214,18 +214,37 @@
|
||||
|
||||
// ═══ FINANCE ═══
|
||||
case "finance":
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px;">
|
||||
<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(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 (!_orders.Any())
|
||||
@if (_walletTxns.Any())
|
||||
{
|
||||
@RenderEmpty("bar-chart-3", "#22C55E", "Chưa có dữ liệu tài chính", "Dữ liệu sẽ tự động cập nhật khi có đơn hàng", "monitor", "Mở POS bán hàng", $"/pos/{ShopId}/{_posVertical}")
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Giao dịch ví gần đây</h3></div>
|
||||
<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);">Mô tả</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Số tiền</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Ngày</th>
|
||||
</tr></thead><tbody>
|
||||
@foreach (var t in _walletTxns.Take(15))
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:600;font-size:13px;">@(t.Description ?? t.ItemName ?? "—")</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:@(t.Amount >= 0 ? "#22C55E" : "#EF4444");">@(t.Amount >= 0 ? "+" : "")@FormatVND(t.Amount)</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--admin-text-tertiary);">@t.CreatedAt.ToString("dd/MM HH:mm")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@if (_orders.Any())
|
||||
{
|
||||
<div class="admin-panel">
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Đơn hàng gần đây</h3></div>
|
||||
<div class="admin-panel__body" style="padding:0;">
|
||||
<table class="admin-table" style="width:100%;"><thead><tr>
|
||||
@@ -247,6 +266,10 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@RenderEmpty("bar-chart-3", "#22C55E", "Chưa có dữ liệu tài chính", "Dữ liệu sẽ tự động cập nhật khi có đơn hàng", "monitor", "Mở POS bán hàng", $"/pos/{ShopId}/{_posVertical}")
|
||||
}
|
||||
break;
|
||||
|
||||
// ═══ STAFF ═══
|
||||
@@ -321,7 +344,7 @@
|
||||
}
|
||||
break;
|
||||
|
||||
// ═══ CUSTOMERS ═══
|
||||
// ═══ CUSTOMERS + MEMBERSHIP LEVELS ═══
|
||||
case "customers":
|
||||
@if (!_members.Any())
|
||||
{
|
||||
@@ -331,8 +354,35 @@
|
||||
{
|
||||
<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(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>
|
||||
<div class="admin-panel">
|
||||
@if (_memberLevels.Any())
|
||||
{
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Cấp bậc thành viên</h3></div>
|
||||
<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);">Level</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Tên</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">EXP cần</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Thành viên</th>
|
||||
</tr></thead><tbody>
|
||||
@foreach (var lvl in _memberLevels.OrderBy(l => l.Level))
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:700;color:var(--admin-orange-primary);">@lvl.Level</td>
|
||||
<td style="padding:12px 16px;font-weight:600;">@lvl.Name</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:13px;color:var(--admin-text-tertiary);">@lvl.MinExp.ToString("N0") — @lvl.MaxExp.ToString("N0")</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;">@lvl.MemberCount</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Danh sách khách hàng</h3></div>
|
||||
<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>
|
||||
@@ -608,9 +658,44 @@
|
||||
@RenderEmpty("clipboard-list", "#A855F7", "Quản lý liệu trình", "Theo dõi liệu trình điều trị dài hạn, ảnh before/after, tiến trình")
|
||||
break;
|
||||
|
||||
// ═══ PROMOTIONS ═══
|
||||
// ═══ PROMOTIONS (real data) ═══
|
||||
case "promotions":
|
||||
@RenderEmpty("tag", "#22C55E", "Quản lý khuyến mãi", "Tạo mã giảm giá, combo, chương trình loyalty — Kết nối Promotion Service", "plus-circle", "Tạo khuyến mãi")
|
||||
@if (!_promotions.Any())
|
||||
{
|
||||
@RenderEmpty("tag", "#22C55E", "Chưa có khuyến mãi", "Tạo mã giảm giá, combo, chương trình loyalty", "plus-circle", "Tạo khuyến mãi")
|
||||
}
|
||||
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(34,197,94,0.1);"><i data-lucide="tag" style="color:#22C55E;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_promotions.Count</span><span class="admin-stat-card__label">Tổng KM</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="zap" style="color:#3B82F6;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_promotions.Count(p => p.IsActive)</span><span class="admin-stat-card__label">Đang hoạt động</span></div></div>
|
||||
<div class="admin-stat-card"><div class="admin-stat-card__icon" style="background:rgba(236,72,153,0.1);"><i data-lucide="ticket" style="color:#EC4899;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_promotions.Sum(p => p.VoucherCount)</span><span class="admin-stat-card__label">Voucher</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="check-circle" style="color:#FF5C00;"></i></div><div class="admin-stat-card__content"><span class="admin-stat-card__value">@_promotions.Sum(p => p.RedemptionCount)</span><span class="admin-stat-card__label">Đã dùng</span></div></div>
|
||||
</div>
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Danh sách khuyến mãi</h3></div>
|
||||
<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);">Tên</th>
|
||||
<th style="padding:12px 16px;text-align:center;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Loại</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Giá trị</th>
|
||||
<th style="padding:12px 16px;text-align:center;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Trạng thái</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Thời gian</th>
|
||||
</tr></thead><tbody>
|
||||
@foreach (var p in _promotions)
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:600;">@p.Name</td>
|
||||
<td style="padding:12px 16px;text-align:center;font-size:12px;color:var(--admin-text-tertiary);">@(p.DiscountType ?? "—")</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:var(--admin-orange-primary);">@(p.DiscountType == "Percentage" ? $"{p.DiscountValue}%" : FormatVND(p.DiscountValue ?? 0))</td>
|
||||
<td style="padding:12px 16px;text-align:center;"><span class="admin-status-badge @(p.IsActive ? "admin-status-badge--online" : "admin-status-badge--offline")" style="font-size:11px;padding:2px 10px;"><span class="admin-status-badge__dot" style="width:5px;height:5px;"></span>@(p.IsActive ? "Active" : "Inactive")</span></td>
|
||||
<td style="padding:12px 16px;font-size:12px;color:var(--admin-text-tertiary);">@(p.StartDate?.ToString("dd/MM/yy") ?? "—") → @(p.EndDate?.ToString("dd/MM/yy") ?? "∞")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
break;
|
||||
|
||||
// ═══ SETTINGS ═══
|
||||
@@ -710,6 +795,13 @@
|
||||
private string? _staffFormMessage;
|
||||
private bool _staffFormSuccess;
|
||||
private Guid? _merchantId;
|
||||
// New data: wallets, promotions, member levels, schedules, inv txns
|
||||
private List<PosDataService.WalletInfo> _wallets = new();
|
||||
private List<PosDataService.WalletTxnInfo> _walletTxns = new();
|
||||
private List<PosDataService.PromotionInfo> _promotions = new();
|
||||
private List<PosDataService.LevelDefinitionInfo> _memberLevels = new();
|
||||
private List<PosDataService.ScheduleInfo> _staffSchedules = new();
|
||||
private List<PosDataService.InventoryTxnInfo> _invTxns = new();
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
|
||||
@@ -764,12 +856,16 @@
|
||||
break;
|
||||
case "finance":
|
||||
_orders = await DataService.GetOrdersAsync(_shopGuid);
|
||||
_wallets = await DataService.GetWalletsAsync();
|
||||
_walletTxns = await DataService.GetWalletTransactionsAsync();
|
||||
break;
|
||||
case "staff":
|
||||
_staff = await DataService.GetStaffAsync();
|
||||
_staffSchedules = await DataService.GetStaffSchedulesAsync(_shopGuid);
|
||||
break;
|
||||
case "customers":
|
||||
_members = await DataService.GetMembersAsync();
|
||||
_memberLevels = await DataService.GetMembershipLevelsAsync();
|
||||
break;
|
||||
case "tables":
|
||||
case "rooms":
|
||||
@@ -787,6 +883,9 @@
|
||||
_reportOrders = await DataService.GetOrdersAsync(_shopGuid);
|
||||
_reportProducts = await DataService.GetAllProductsAsync(_shopGuid);
|
||||
break;
|
||||
case "promotions":
|
||||
_promotions = await DataService.GetPromotionsAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user