From abd709d31c51e0893c9a337d5eb217d403a51387 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sat, 28 Feb 2026 06:56:27 +0700 Subject: [PATCH] =?UTF-8?q?fix(web-client):=20audit=20fixes=20=E2=80=94=20?= =?UTF-8?q?real=20shop=20stats,=20dynamic=20CTA=20links,=20shared=20helper?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - C1: StoreList shows real per-shop stats (revenue/orders/staff/products) via new BFF endpoint - C2: RenderEmpty CTA links now route to correct section (finance→/pos, inventory→/menu, etc.) - C3+M5: ShopOverview right column shows real recent orders instead of always-empty - C4: AdminSettings expanded from 5 hardcoded services to 11 with icons - M1-M3: Consolidated duplicate helpers (GetShopIcon, GetStatusBadgeClass, GetStatusLabel) into ShopVerticalHelper - M2: Added proper error state UI for StoreList data loading failures - Added GET /api/bff/shops/stats BFF endpoint for aggregated per-shop statistics --- .../Pages/Admin/AdminSettings.razor | 26 +++- .../Pages/Admin/Shop/ShopOverview.razor | 48 ++++--- .../Pages/Admin/Shop/ShopPage.razor | 16 +-- .../Pages/Admin/Store/StoreList.razor | 131 ++++++++---------- .../Services/PosDataService.cs | 7 + .../Services/ShopVerticalHelper.cs | 67 +++++++++ .../Controllers/BffDataController.cs | 78 +++++++++++ 7 files changed, 270 insertions(+), 103 deletions(-) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor index 5ba2261f..20b44918 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor @@ -80,11 +80,12 @@ @foreach (var svc in _services) {
+ + @svc.Name
Online
- @svc
} @@ -202,7 +203,22 @@ private string _tab = "general"; private int _shopCount = 0; - private readonly string[] _services = { "API Gateway", "IAM Service", "Merchant Service", "Catalog Service", "Order Service" }; + // EN: Service list matching actual deployed Docker containers + // VI: Danh sách dịch vụ khớp với Docker containers thực tế + private readonly (string Name, string Icon)[] _services = new[] + { + ("IAM Service", "shield"), + ("Merchant Service", "store"), + ("Catalog Service", "package"), + ("Order Service", "shopping-bag"), + ("Inventory Service", "warehouse"), + ("Wallet Service", "wallet"), + ("Membership Service", "users"), + ("Promotion Service", "tag"), + ("Booking Service", "calendar"), + ("F&B Engine", "utensils"), + ("Storage Service", "hard-drive"), + }; protected override async Task OnInitializedAsync() { @@ -211,7 +227,11 @@ var shops = await DataService.GetShopsAsync(); _shopCount = shops.Count; } - catch { _shopCount = 0; } + catch (Exception ex) + { + _shopCount = 0; + Console.Error.WriteLine($"[AdminSettings] Error loading shop count: {ex.Message}"); + } } private record NotifSetting(string Label, string Desc, bool Enabled); diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopOverview.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopOverview.razor index 4cb82650..6f698324 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopOverview.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopOverview.razor @@ -161,10 +161,33 @@ Đơn gần nhất -
- -

Chưa có đơn hàng nào

-
+
+ @if (_orders.Any()) + { +
+ @foreach (var o in _orders.Take(5)) + { +
+
+ @o.Id.ToString()[..8] + @o.CreatedAt.ToString("dd/MM HH:mm") +
+
+ @(o.Status ?? "—") + @FormatVND(o.TotalAmount) +
+
+ } +
+ } + else + { +
+ +

Chưa có đơn hàng nào

+
+ } +
@@ -235,21 +258,8 @@ private static string FormatVND(decimal val) => val.ToString("N0") + " ₫"; - private static string GetStatusBadgeClass(string? status) => status?.ToLowerInvariant() switch - { - "published" or "active" => "online", - "draft" or "setup" => "setup", - "inactive" or "paused" => "paused", - _ => "setup" - }; + private static string GetStatusBadgeClass(string? status) => ShopVerticalHelper.GetStatusBadgeClass(status); - private static string GetStatusLabel(string? status) => status?.ToLowerInvariant() switch - { - "published" or "active" => "Đang mở", - "draft" or "setup" => "Thiết lập", - "inactive" or "paused" => "Tạm dừng", - "closed" => "Đã đóng", - _ => status ?? "—" - }; + private static string GetStatusLabel(string? status) => ShopVerticalHelper.GetStatusLabel(status); } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopPage.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopPage.razor index e2ea8f0e..82c37915 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopPage.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopPage.razor @@ -54,7 +54,7 @@ case "products": @if (!_products.Any()) { - @RenderEmpty("coffee", "#F59E0B", "Chưa có sản phẩm", "Thêm sản phẩm để bắt đầu bán hàng", "plus-circle", "Thêm sản phẩm") + @RenderEmpty("coffee", "#F59E0B", "Chưa có sản phẩm", "Thêm sản phẩm để bắt đầu bán hàng", "plus-circle", "Thêm sản phẩm", $"/admin/shop/{ShopId}/menu") } else { @@ -78,7 +78,7 @@ case "inventory": @if (!_inventory.Any()) { - @RenderEmpty("warehouse", "#3B82F6", "Chưa có tồn kho", "Tồn kho sẽ hiển thị khi có sản phẩm", "package", "Thêm sản phẩm trước") + @RenderEmpty("warehouse", "#3B82F6", "Chưa có tồn kho", "Tồn kho sẽ hiển thị khi có sản phẩm", "package", "Thêm sản phẩm trước", $"/admin/shop/{ShopId}/menu") } else { @@ -117,7 +117,7 @@
@if (!_orders.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") + @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") } else { @@ -149,7 +149,7 @@ case "staff": @if (!_staff.Any()) { - @RenderEmpty("users", "#8B5CF6", "Chưa có nhân viên", "Thêm nhân viên để quản lý cửa hàng", "user-plus", "Thêm nhân viên") + @RenderEmpty("users", "#8B5CF6", "Chưa có nhân viên", "Thêm nhân viên để quản lý cửa hàng", "user-plus", "Thêm nhân viên", $"/admin/shop/{ShopId}/staff") } else { @@ -184,7 +184,7 @@ case "customers": @if (!_members.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") + @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") } else { @@ -339,8 +339,8 @@ private static string FormatVND(decimal val) => val.ToString("N0") + " ₫"; - // EN: Reusable empty state renderer with optional CTA / VI: Renderer trạng thái trống tái sử dụng với CTA tùy chọn - private RenderFragment RenderEmpty(string icon, string color, string title, string desc, string? ctaIcon = null, string? ctaLabel = null) => __builder => + // EN: Reusable empty state renderer with dynamic CTA href / VI: Renderer trạng thái trống với CTA href động + private RenderFragment RenderEmpty(string icon, string color, string title, string desc, string? ctaIcon = null, string? ctaLabel = null, string? ctaHref = null) => __builder => {
@@ -350,7 +350,7 @@

@desc

@if (ctaIcon != null && ctaLabel != null) { - + @ctaLabel diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Store/StoreList.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Store/StoreList.razor index f062506b..d7153e58 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Store/StoreList.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Store/StoreList.razor @@ -37,22 +37,38 @@
@* ═══ STORE LIST ═══ *@
- @if (IsLoading) + @if (!string.IsNullOrEmpty(_errorMessage)) + { +
+
+
+ +
+

Lỗi tải dữ liệu

+

@_errorMessage

+ +
+
+ } + else if (IsLoading) {
@@ -96,43 +112,44 @@ { @foreach (var shop in FilteredShops) { - var isPaused = IsPaused(shop.Status); + var isPaused = ShopVerticalHelper.IsPaused(shop.Status); + var stats = GetStatsForShop(shop.Id);
-
- +
+
@shop.Name -
+
- @GetStatusLabel(shop.Status) + @ShopVerticalHelper.GetStatusLabel(shop.Status)
- @(shop.Category ?? "—") • @(shop.Slug) + @ShopVerticalHelper.GetLabel(shop.Category) • @(shop.Slug)
-
--
+
@FormatRevenue(stats.Revenue)
Doanh thu
-
--
+
@stats.OrderCount
Đơn hàng
-
--
+
@stats.StaffCount
Nhân viên
-
--
+
@stats.ProductCount
Sản phẩm
- @if (IsSetup(shop.Status)) + @if (ShopVerticalHelper.IsSetup(shop.Status)) {