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 82c37915..d0760a59 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 @@ -214,7 +214,225 @@ } break; - // ═══ PLACEHOLDER SECTIONS (POS, Tables, Kitchen, Rooms, Appointments, Services, Reports) ═══ + // ═══ TABLES (Restaurant) ═══ + case "tables": + @if (!_tables.Any()) + { + @RenderEmpty("grid-3x3", "#F59E0B", "Chưa có bàn nào", "Thêm bàn để quản lý sơ đồ phục vụ", "plus-circle", "Thêm bàn") + } + else + { +
+
+ Trống: @_tables.Count(t => t.Status == "available") + Đang dùng: @_tables.Count(t => t.Status == "occupied") + Đã đặt: @_tables.Count(t => t.Status == "reserved") +
+
+
+ @foreach (var table in _tables) + { + var bgColor = table.Status switch { "available" => "rgba(34,197,94,0.08)", "occupied" => "rgba(239,68,68,0.08)", "reserved" => "rgba(245,158,11,0.08)", _ => "rgba(107,107,111,0.08)" }; + var borderColor = table.Status switch { "available" => "rgba(34,197,94,0.3)", "occupied" => "rgba(239,68,68,0.3)", "reserved" => "rgba(245,158,11,0.3)", _ => "rgba(107,107,111,0.3)" }; + var statusColor = table.Status switch { "available" => "#22C55E", "occupied" => "#EF4444", "reserved" => "#F59E0B", _ => "#6B6B6F" }; + var statusText = table.Status switch { "available" => "Trống", "occupied" => "Đang dùng", "reserved" => "Đã đặt", "cleaning" => "Dọn dẹp", _ => table.Status }; +
+
@table.TableNumber
+
@(table.Zone ?? "Chung") • @table.Capacity chỗ
+
+ + @statusText +
+ @if (table.SessionId.HasValue) + { +
+ @table.GuestCount khách • @(table.StartedAt?.ToString("HH:mm") ?? "—") +
+ } +
+ } +
+ } + break; + + // ═══ ROOMS (Karaoke — reuse tables structure) ═══ + case "rooms": + @if (!_tables.Any()) + { + @RenderEmpty("door-open", "#8B5CF6", "Chưa có phòng nào", "Thêm phòng để quản lý Karaoke", "plus-circle", "Thêm phòng") + } + else + { +
+
+ Trống: @_tables.Count(t => t.Status == "available") + Đang hát: @_tables.Count(t => t.Status == "occupied") + Đã đặt: @_tables.Count(t => t.Status == "reserved") +
+
+
+ @foreach (var room in _tables) + { + var bgColor = room.Status switch { "available" => "rgba(139,92,246,0.08)", "occupied" => "rgba(239,68,68,0.08)", "reserved" => "rgba(245,158,11,0.08)", _ => "rgba(107,107,111,0.08)" }; + var borderColor = room.Status switch { "available" => "rgba(139,92,246,0.3)", "occupied" => "rgba(239,68,68,0.3)", "reserved" => "rgba(245,158,11,0.3)", _ => "rgba(107,107,111,0.3)" }; + var statusColor = room.Status switch { "available" => "#8B5CF6", "occupied" => "#EF4444", "reserved" => "#F59E0B", _ => "#6B6B6F" }; + var statusText = room.Status switch { "available" => "Trống", "occupied" => "Đang hát", "reserved" => "Đã đặt", "cleaning" => "Dọn dẹp", _ => room.Status }; +
+
+ + Phòng @room.TableNumber +
+
@(room.Zone ?? "Chung") • @room.Capacity chỗ
+
+ + @statusText +
+ @if (room.SessionId.HasValue) + { +
+ @room.GuestCount khách • Bắt đầu @(room.StartedAt?.ToString("HH:mm") ?? "—") +
+ } +
+ } +
+ } + break; + + // ═══ APPOINTMENTS (Spa / Thẩm mỹ) ═══ + case "appointments": + @if (!_appointments.Any()) + { + @RenderEmpty("calendar", "#EC4899", "Chưa có lịch hẹn", "Lịch hẹn sẽ hiển thị khi khách đặt dịch vụ", "plus-circle", "Tạo lịch hẹn", $"/admin/shop/{ShopId}/appointments") + } + else + { +
+
@_appointments.CountTổng lịch hẹn
+
@_appointments.Count(a => a.Status == "Confirmed")Đã xác nhận
+
@_appointments.Count(a => a.Status == "Pending")Chờ xác nhận
+
+
+
+ + + + + + @foreach (var a in _appointments.OrderBy(a => a.StartTime)) + { + var statusColor = a.Status switch { "Confirmed" => "#22C55E", "Pending" => "#F59E0B", "Completed" => "#3B82F6", "Cancelled" => "#EF4444", _ => "#6B6B6F" }; + + + + + + } +
Thời gianPhòng / Tài nguyênTrạng thái
@a.StartTime.ToString("dd/MM/yyyy")
@a.StartTime.ToString("HH:mm") — @a.EndTime.ToString("HH:mm")
@(a.ResourceName ?? "—")@a.Status
+
+
+ } + break; + + // ═══ SERVICES (Spa — products with type=Service) ═══ + case "services": + @if (!_products.Any(p => p.Type == "Service")) + { + @RenderEmpty("sparkles", "#EC4899", "Chưa có dịch vụ", "Thêm dịch vụ để khách có thể đặt lịch", "plus-circle", "Thêm dịch vụ", $"/admin/shop/{ShopId}/menu") + } + else + { +
+
+ + + + + + @foreach (var s in _products.Where(p => p.Type == "Service")) + { + + + + + + } +
Dịch vụGiáTrạng thái
@s.Name
@(s.Description ?? "—")
@FormatVND(s.Price)@(s.IsActive ? "Hoạt động" : "Tạm ngưng")
+
+
+ } + break; + + // ═══ POS — Redirect prompt ═══ + case "pos": +
+
+
+ +
+

POS Bán hàng

+

Mở giao diện bán hàng tại điểm để tạo đơn, thanh toán và in hóa đơn.

+ + + Mở POS + +
+
+ break; + + // ═══ REPORTS — Aggregate stats ═══ + case "reports": +
+
@FormatVND(_reportOrders.Sum(o => o.TotalAmount))Tổng doanh thu
+
@_reportOrders.CountTổng đơn hàng
+
@FormatVND(_reportOrders.Any() ? _reportOrders.Average(o => o.TotalAmount) : 0)Giá trị TB / đơn
+
@_reportProducts.CountSản phẩm
+
+ @if (_reportOrders.Any()) + { +
+

Đơn hàng gần nhất

+
+ + + + + + + @foreach (var o in _reportOrders.Take(20)) + { + + + + + + + } +
Mã đơnGiá trịTrạng tháiNgày tạo
@o.Id.ToString()[..8]@FormatVND(o.TotalAmount)@(o.Status ?? "—")@o.CreatedAt.ToString("dd/MM/yyyy HH:mm")
+
+
+ } + else + { + @RenderEmpty("bar-chart-2", "#22C55E", "Chưa có dữ liệu báo cáo", "Dữ liệu sẽ hiển thị khi có đơn hàng và hoạt động kinh doanh") + } + break; + + // ═══ KITCHEN — Placeholder (needs KDS) ═══ + case "kitchen": +
+
+
+ +
+

Kitchen Display System

+

Màn hình hiển thị đơn cho bếp (KDS) — tự động nhận đơn từ POS, phân luồng theo trạm bếp.

+

Tính năng KDS sẽ hoạt động khi kết nối với F&B Engine

+
+
+ break; + + // ═══ UNKNOWN SECTIONS ═══ default:
@@ -222,12 +440,7 @@

@_sectionTitle

-

- @_sectionDescription -

-

- Tính năng này sẽ được kích hoạt khi có dữ liệu từ hệ thống -

+

@_sectionDescription

break; @@ -255,6 +468,11 @@ private List _orders = new(); private List _staff = new(); private List _members = new(); + private List _tables = new(); + private List _appointments = new(); + // Reports data (separate from section-specific _orders) + private List _reportOrders = new(); + private List _reportProducts = new(); protected override async Task OnInitializedAsync() => await LoadData(); @@ -305,6 +523,22 @@ case "customers": _members = await DataService.GetMembersAsync(); break; + case "tables": + case "rooms": + if (_shopGuid.HasValue) + _tables = await DataService.GetTablesAsync(_shopGuid.Value); + break; + case "appointments": + if (_shopGuid.HasValue) + _appointments = await DataService.GetAppointmentsAsync(_shopGuid.Value); + break; + case "services": + _products = await DataService.GetAllProductsAsync(_shopGuid); + break; + case "reports": + _reportOrders = await DataService.GetOrdersAsync(_shopGuid); + _reportProducts = await DataService.GetAllProductsAsync(_shopGuid); + break; } } catch (Exception ex)