- @if (IsLoading)
+ @if (!string.IsNullOrEmpty(_errorMessage))
+ {
+
+
+
+
+
+
Lỗi tải dữ liệu
+
@_errorMessage
+
+
+
+ }
+ else if (IsLoading)
{
@@ -33,22 +49,12 @@
{
@switch (_section)
{
- // ═══ OVERVIEW ═══
- case "overview":
-
- break;
-
// ═══ MENU / PRODUCTS ═══
case "menu":
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")
+ @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")
}
else
{
@@ -72,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")
+ @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")
}
else
{
@@ -111,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")
+ @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")
}
else
{
@@ -143,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")
+ @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")
}
else
{
@@ -178,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")
+ @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")
}
else
{
@@ -240,6 +246,7 @@
private string _sectionTitle = "";
private string _sectionIcon = "layout-dashboard";
private string _sectionDescription = "";
+ private string? _errorMessage;
private Guid? _shopGuid;
// ═══ DATA ═══
@@ -261,6 +268,7 @@
private async Task LoadData()
{
IsLoading = true;
+ _errorMessage = null;
_section = Section?.ToLowerInvariant() ?? "";
ConfigureSection();
@@ -281,12 +289,6 @@
// EN: Load only data needed for current section / VI: Chỉ tải data cần cho section hiện tại
switch (_section)
{
- case "overview":
- _products = await DataService.GetAllProductsAsync(_shopGuid);
- _inventory = await DataService.GetInventoryAsync(_shopGuid);
- _orders = await DataService.GetOrdersAsync(_shopGuid);
- _staff = await DataService.GetStaffAsync();
- break;
case "menu":
case "products":
_products = await DataService.GetAllProductsAsync(_shopGuid);
@@ -305,7 +307,11 @@
break;
}
}
- catch { }
+ catch (Exception ex)
+ {
+ _errorMessage = $"Không thể tải dữ liệu: {ex.Message}";
+ Console.Error.WriteLine($"[ShopPage] Error loading {_section}: {ex}");
+ }
finally { IsLoading = false; }
}
@@ -331,17 +337,24 @@
}
}
- private static string FormatVND(decimal val) => val.ToString("N0") + "₫";
+ private static string FormatVND(decimal val) => val.ToString("N0") + " ₫";
- // EN: Reusable empty state renderer / VI: Renderer trạng thái trống tái sử dụng
- private RenderFragment RenderEmpty(string icon, string color, string title, string desc) => __builder =>
+ // 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 =>
{
@title
-
@desc
+
@desc
+ @if (ctaIcon != null && ctaLabel != null)
+ {
+
+
+ @ctaLabel
+
+ }
};
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Attendance.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Attendance.razor
deleted file mode 100644
index b1fe21d1..00000000
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Attendance.razor
+++ /dev/null
@@ -1,83 +0,0 @@
-@page "/admin/staff/attendance"
-@layout AdminLayout
-@inherits AdminBase
-@inject PosDataService DataService
-@using WebClientTpos.Client.Services
-
-
Chấm công — GoodGo Admin
-
-
-
-
Chấm công
-
Theo dõi giờ làm việc nhân viên
-
-
-
-
-
-
-
-
@_staff.Count(s => s.Status == "Active")Đang hoạt động
-
-
-
-
@_staff.CountTổng nhân viên
-
-
-
-
@_schedules.CountTổng ca hôm nay
-
-
-
- @if (IsLoading)
- {
-
- }
- else
- {
-
-
-
-
-
- | Nhân viên |
- Vai trò |
- Trạng thái |
- Cửa hàng |
-
-
- @foreach (var s in _staff)
- {
-
- | @(s.EmployeeCode ?? s.Id.ToString()[..6]) |
- @(s.Role ?? "—") |
-
-
- @(s.Status ?? "—")
-
- |
- @(s.ShopName ?? "—") |
-
- }
-
-
-
-
- }
-
-
-@code {
- private List
_staff = new();
- private List _schedules = new();
-
- protected override async Task OnInitializedAsync()
- {
- IsLoading = true;
- try
- {
- _staff = await DataService.GetStaffAsync();
- _schedules = await DataService.GetStaffSchedulesAsync();
- }
- catch { } finally { IsLoading = false; }
- }
-}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Payroll.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Payroll.razor
deleted file mode 100644
index a4e3dc07..00000000
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/Payroll.razor
+++ /dev/null
@@ -1,73 +0,0 @@
-@page "/admin/staff/payroll"
-@layout AdminLayout
-@inherits AdminBase
-@inject PosDataService DataService
-@using WebClientTpos.Client.Services
-
-Bảng lương — GoodGo Admin
-
-
-
-
Bảng lương
-
@_staff.Count nhân viên • @DateTime.Now.ToString("MM/yyyy")
-
-
-
-
- @if (IsLoading)
- {
-
- }
- else if (!_staff.Any())
- {
-
-
-
-
-
Chưa có dữ liệu lương
-
Thêm nhân viên để quản lý bảng lương
-
- }
- else
- {
-
-
-
-
-
- | Mã NV |
- Vai trò |
- Ngày vào |
- Trạng thái |
-
-
- @foreach (var s in _staff)
- {
-
- | @(s.EmployeeCode ?? "—") |
- @(s.Role ?? "—") |
- @(s.JoinedAt?.ToString("dd/MM/yyyy") ?? "—") |
-
-
- @(s.Status ?? "—")
-
- |
-
- }
-
-
-
-
- }
-
-
-@code {
- private List _staff = new();
-
- protected override async Task OnInitializedAsync()
- {
- IsLoading = true;
- try { _staff = await DataService.GetStaffAsync(); }
- catch { } finally { IsLoading = false; }
- }
-}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffSchedule.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffSchedule.razor
deleted file mode 100644
index c90adf01..00000000
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffSchedule.razor
+++ /dev/null
@@ -1,84 +0,0 @@
-@page "/admin/staff/schedule"
-@layout AdminLayout
-@inherits AdminBase
-@inject PosDataService DataService
-@using WebClientTpos.Client.Services
-
-Lịch làm việc — GoodGo Admin
-
-
-
-
Lịch làm việc
-
@_schedules.Count ca làm việc
-
-
-
-
-
-
-
- @if (IsLoading)
- {
-
- }
- else if (!_schedules.Any())
- {
-
-
-
-
-
Chưa có lịch làm việc
-
Thêm lịch cho nhân viên để quản lý ca làm việc
-
- }
- else
- {
- @foreach (var dayGroup in _schedules.GroupBy(s => s.DayOfWeek).OrderBy(g => g.Key))
- {
-
-
-
- @foreach (var s in dayGroup)
- {
-
-
- @(s.EmployeeCode ?? s.StaffId.ToString()[..6])
- @(s.Role ?? "")
-
-
@s.StartTime — @s.EndTime
-
- }
-
-
- }
- }
-
-
-@code {
- private List _schedules = new();
- private List _shops = new();
- private Guid? _selectedShopId;
-
- protected override async Task OnInitializedAsync()
- {
- IsLoading = true;
- try { _shops = await DataService.GetShopsAsync(); _schedules = await DataService.GetStaffSchedulesAsync(); }
- catch { } finally { IsLoading = false; }
- }
-
- private async Task OnShopFilterChanged(ChangeEventArgs e)
- {
- _selectedShopId = Guid.TryParse(e.Value?.ToString(), out var id) ? id : null;
- IsLoading = true;
- try { _schedules = await DataService.GetStaffSchedulesAsync(_selectedShopId); }
- catch { } finally { IsLoading = false; }
- }
-
- private static string DayName(int day) => day switch {
- 0 => "Chủ nhật", 1 => "Thứ 2", 2 => "Thứ 3", 3 => "Thứ 4", 4 => "Thứ 5", 5 => "Thứ 6", 6 => "Thứ 7", _ => $"Ngày {day}"
- };
-}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/NotificationCenter.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/NotificationCenter.razor
deleted file mode 100644
index f1f2f3b3..00000000
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/NotificationCenter.razor
+++ /dev/null
@@ -1,42 +0,0 @@
-@page "/admin/system/notifications"
-@layout AdminLayout
-@inherits AdminBase
-
-Thông báo — GoodGo Admin
-
-
-
-
Trung tâm thông báo
-
Quản lý thông báo đẩy
-
-
-
-
-
-
-
- @foreach (var tmpl in _templates)
- {
-
-
-
@tmpl.name
-
@tmpl.desc
-
-
@(tmpl.active ? "Bật" : "Tắt")
-
- }
-
-
-
-
-@code {
- private readonly (string name, string desc, bool active)[] _templates = new[]
- {
- ("Đơn hàng mới", "Thông báo khi có đơn hàng mới", true),
- ("Hết hàng", "Cảnh báo khi sản phẩm hết hàng", true),
- ("Khuyến mãi", "Thông báo chương trình khuyến mãi", false),
- ("Đặt lịch", "Nhắc nhở lịch hẹn", true),
- ("Chấm công", "Thông báo chấm công", false),
- ("Hệ thống", "Thông báo bảo trì hệ thống", true),
- };
-}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/ShopSidebarConfig.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/ShopSidebarConfig.cs
index 39eb92ef..17662252 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/ShopSidebarConfig.cs
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/ShopSidebarConfig.cs
@@ -24,7 +24,7 @@ public static class ShopSidebarConfig
///
public static List