From c0301a22e52167ac516f34a19904d9980ef026d7 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Thu, 5 Mar 2026 05:03:28 +0700 Subject: [PATCH] feat(pos): Display empty state messages for resource grids and implement shop-specific staff management in POS views. --- .../Pages/Admin/Shop/ShopPage.razor | 16 ++++++++-------- .../Pages/Pos/Karaoke/KaraokeDesktop.razor | 11 +++++++++++ .../Pages/Pos/Restaurant/RestaurantDesktop.razor | 11 +++++++++++ .../Pages/Pos/Spa/SpaDesktop.razor | 11 +++++++++++ .../Services/PosDataService.cs | 12 +++++++++++- .../Application/Queries/GetTablesQueryHandler.cs | 6 +++++- 6 files changed, 57 insertions(+), 10 deletions(-) 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 ce18229a..9c189d15 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 @@ -2861,7 +2861,7 @@ case "overview": _ovOrders = await DataService.GetOrdersAsync(_shopGuid); _ovProducts = await DataService.GetAllProductsAsync(_shopGuid); - _ovStaff = await DataService.GetStaffAsync(); + _ovStaff = await DataService.GetStaffForShopAsync(_shopGuid.Value); if (_shopGuid.HasValue && (_posVertical == "restaurant" || _posVertical == "karaoke")) _ovTables = await DataService.GetTablesAsync(_shopGuid.Value); if (_shopGuid.HasValue && (_posVertical == "spa" || _posVertical == "beauty")) @@ -2882,7 +2882,7 @@ _walletTxns = await DataService.GetWalletTransactionsAsync(); break; case "staff": - _staff = await DataService.GetStaffAsync(); + _staff = await DataService.GetStaffForShopAsync(_shopGuid.Value); _staffSchedules = await DataService.GetStaffSchedulesAsync(_shopGuid); break; case "customers": @@ -2922,7 +2922,7 @@ case "shifts": case "schedule": _staffSchedules = await DataService.GetStaffSchedulesAsync(_shopGuid); - _staff = await DataService.GetStaffAsync(); + _staff = await DataService.GetStaffForShopAsync(_shopGuid.Value); break; case "kitchen": if (_shopGuid.HasValue) @@ -3008,7 +3008,7 @@ case "inventory": _sectionTitle = "Tồn kho"; _sectionIcon = "warehouse"; _sectionDescription = "Theo dõi tồn kho, cảnh báo hết hàng."; break; case "finance": _sectionTitle = "Tài chính"; _sectionIcon = "trending-up"; _sectionDescription = "Doanh thu, đơn hàng, chi phí."; break; case "staff": _sectionTitle = "Nhân sự"; _sectionIcon = "users"; _sectionDescription = "Quản lý nhân viên cửa hàng."; break; - case "customers": _sectionTitle = "Khách hàng"; _sectionIcon = "heart"; _sectionDescription = "Khách hàng, thành viên."; break; + case "customers": _sectionTitle = "Khách hàng"; _sectionIcon = "heart"; _sectionDescription = "Khách hàng & thành viên — dữ liệu chung cho tất cả cửa hàng."; break; case "pos": _sectionTitle = "POS Bán hàng"; _sectionIcon = "monitor"; _sectionDescription = "Mở giao diện bán hàng tại điểm."; break; case "tables": _sectionTitle = "Quản lý Bàn"; _sectionIcon = "grid-3x3"; _sectionDescription = "Sơ đồ bàn, khu vực phục vụ."; break; case "kitchen": _sectionTitle = "Bếp (Kitchen)"; _sectionIcon = "flame"; _sectionDescription = "Màn hình hiển thị đơn cho bếp."; break; @@ -3017,7 +3017,7 @@ case "services": _sectionTitle = "Dịch vụ"; _sectionIcon = "sparkles"; _sectionDescription = "Quản lý danh mục dịch vụ."; break; case "resources": _sectionTitle = "Tài nguyên"; _sectionIcon = "door-open"; _sectionDescription = "Quản lý phòng, giường, thiết bị."; break; case "treatments": _sectionTitle = "Liệu trình"; _sectionIcon = "clipboard-list"; _sectionDescription = "Theo dõi liệu trình điều trị."; break; - case "promotions": _sectionTitle = "Khuyến mãi"; _sectionIcon = "tag"; _sectionDescription = "Quản lý chương trình khuyến mãi."; break; + case "promotions": _sectionTitle = "Khuyến mãi"; _sectionIcon = "tag"; _sectionDescription = "Chương trình khuyến mãi — dữ liệu chung cho tất cả cửa hàng."; break; case "settings": _sectionTitle = "Thiết lập"; _sectionIcon = "settings"; _sectionDescription = "Cài đặt cửa hàng."; break; case "reports": _sectionTitle = "Báo cáo"; _sectionIcon = "bar-chart-2"; _sectionDescription = "Doanh thu, sản phẩm bán chạy."; break; case "schedule": _sectionTitle = "Lịch làm việc"; _sectionIcon = "calendar-clock"; _sectionDescription = "Lịch ca làm việc nhân viên."; break; @@ -3213,7 +3213,7 @@ } _newStaffCode = ""; _newStaffPhone = ""; _newStaffEmail = ""; _newStaffFirstName = ""; _newStaffLastName = ""; _newStaffPassword = ""; _newStaffAddress = ""; _createStaffAccount = false; _staffDocFrontFile = null; _staffDocBackFile = null; _staffDocFrontPreview = null; _staffDocBackPreview = null; - _staff = await DataService.GetStaffAsync(); + _staff = await DataService.GetStaffForShopAsync(_shopGuid.Value); } catch (Exception ex) { _staffFormMessage = $"Lỗi: {ex.Message}"; _staffFormSuccess = false; } } @@ -3252,7 +3252,7 @@ _staffFormMessage = $"Đã cập nhật NV '{_newStaffCode}' thành công!"; _staffFormSuccess = true; _editingStaffId = null; _staffDocFrontFile = null; _staffDocBackFile = null; _staffDocFrontPreview = null; _staffDocBackPreview = null; - _staff = await DataService.GetStaffAsync(); + _staff = await DataService.GetStaffForShopAsync(_shopGuid.Value); } catch (Exception ex) { _staffFormMessage = $"Lỗi: {ex.Message}"; _staffFormSuccess = false; } } @@ -3262,7 +3262,7 @@ try { await DataService.DeleteStaffAsync(staffId); - _staff = await DataService.GetStaffAsync(); + _staff = await DataService.GetStaffForShopAsync(_shopGuid.Value); } catch (Exception ex) { _errorMessage = $"Không thể xóa nhân viên: {ex.Message}"; } } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor index 19613efb..e0b03e10 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor @@ -41,6 +41,16 @@ @* ═══ ROOM GRID / LƯỚI PHÒNG ═══ *@ + @if (!_rooms.Any()) + { +
+ +
Chưa có phòng nào
+
Vui lòng thêm phòng trong phần Quản lý cửa hàng
+
+ } + else + {
@foreach (var room in FilteredRooms) { @@ -70,6 +80,7 @@ }
} + } @* ═══ SESSION PANEL (RIGHT) / PANEL PHIÊN HÁT (PHẢI) ═══ *@ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Restaurant/RestaurantDesktop.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Restaurant/RestaurantDesktop.razor index 53c3021a..7a2eed82 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Restaurant/RestaurantDesktop.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Restaurant/RestaurantDesktop.razor @@ -34,6 +34,16 @@ @* ═══ TABLE MAP GRID / LƯỚI SƠ ĐỒ BÀN ═══ *@ + @if (!_tables.Any()) + { +
+ +
Chưa có bàn nào
+
Vui lòng thêm bàn trong phần Quản lý cửa hàng
+
+ } + else + {
@foreach (var table in FilteredTables) { @@ -49,6 +59,7 @@
} + } } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Spa/SpaDesktop.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Spa/SpaDesktop.razor index 0422197d..734c1023 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Spa/SpaDesktop.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Spa/SpaDesktop.razor @@ -35,6 +35,16 @@ @* ═══ SERVICE GRID / LƯỚI DỊCH VỤ ═══ *@ + @if (!_services.Any()) + { +
+ +
Chưa có dịch vụ nào
+
Vui lòng thêm dịch vụ trong phần Quản lý cửa hàng
+
+ } + else + {
@foreach (var svc in FilteredServices) { @@ -50,6 +60,7 @@
} + } } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs index 8b856fb3..74431b1c 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs @@ -142,8 +142,18 @@ public class PosDataService public record CategoryInfo(Guid Id, string Name, string? Description, int DisplayOrder, string? ImageUrl = null); public record TableInfo(Guid Id, string TableNumber, int Capacity, string? Zone, string Status, Guid? SessionId, int? GuestCount, DateTime? StartedAt); public record AppointmentInfo(Guid Id, Guid? CustomerId, Guid? StaffId, Guid? ResourceId, Guid ServiceId, DateTime StartTime, DateTime EndTime, string Status, string? ResourceName); + public record ShopAssignmentInfo(Guid ShopId, string? ShopRole, Guid? BranchId); public record StaffInfo(Guid Id, Guid? UserId, string? EmployeeCode, string? Phone, string? Email, DateTime? JoinedAt, DateTime? TerminatedAt, string? Role, string? Status, string? ShopName, - string? FirstName = null, string? LastName = null, string? Address = null, string? ProfilePhotoUrl = null, string? DocumentFrontUrl = null, string? DocumentBackUrl = null); + string? FirstName = null, string? LastName = null, string? Address = null, string? ProfilePhotoUrl = null, string? DocumentFrontUrl = null, string? DocumentBackUrl = null, + List? ShopAssignments = null); + + public async Task> GetStaffForShopAsync(Guid shopId) + { + var all = await GetStaffAsync(); + return all.Where(s => + s.ShopAssignments == null || s.ShopAssignments.Count == 0 || + s.ShopAssignments.Any(a => a.ShopId == shopId)).ToList(); + } public async Task> GetShopsAsync() => await GetListFromApiAsync("api/bff/shops"); diff --git a/services/fnb-engine-net/src/FnbEngine.API/Application/Queries/GetTablesQueryHandler.cs b/services/fnb-engine-net/src/FnbEngine.API/Application/Queries/GetTablesQueryHandler.cs index dbaf0a36..26d4e7ae 100644 --- a/services/fnb-engine-net/src/FnbEngine.API/Application/Queries/GetTablesQueryHandler.cs +++ b/services/fnb-engine-net/src/FnbEngine.API/Application/Queries/GetTablesQueryHandler.cs @@ -3,6 +3,7 @@ using MediatR; using FnbEngine.Domain.AggregatesModel.TableAggregate; +using FnbEngine.Domain.SeedWork; namespace FnbEngine.API.Application.Queries; @@ -23,13 +24,16 @@ public class GetTablesQueryHandler : IRequestHandler() + .ToDictionary(s => s.Id, s => s.Name.ToLowerInvariant()); + return tables.Select(t => new TableDto( t.Id, t.ShopId, t.TableNumber, t.Capacity, t.Zone, - t.Status.Name + allStatuses.TryGetValue(t.StatusId, out var name) ? name : "available" )); } }