From 88cd45c3a8b50e239331982e2db12daf52c6aab0 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Thu, 5 Mar 2026 04:40:06 +0700 Subject: [PATCH] feat(pos-cafe): implement payment method settings, add detailed order history view, and update payment icons to Lucide --- .../Pages/Pos/Cafe/CafeDesktop.razor | 422 +++++++++++++++++- .../WebClientTpos.Client/wwwroot/css/pos.css | 40 ++ 2 files changed, 447 insertions(+), 15 deletions(-) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/CafeDesktop.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/CafeDesktop.razor index 4114dd32..bfe76182 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/CafeDesktop.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/CafeDesktop.razor @@ -126,21 +126,30 @@
+ @if (_payCardEnabled) + { + } + @if (_payQrEnabled) + { + } + @if (_payTransferEnabled) + { + }
} @@ -152,7 +161,7 @@ - 💵 Tiền mặt + Tiền mặt @FormatPrice(FinalTotal)
@@ -215,12 +224,12 @@ } else if (_selectedMethod == "card") { -
💳
+
Chạm, quẹt hoặc cắm thẻ
} else { -
🏦
+
Xác nhận đã nhận chuyển khoản
}
@@ -261,6 +270,122 @@ case PosTab.History: @* ═══ ORDER HISTORY TAB ═══ *@
+ @if (_selectedOrderDetail != null) + { + @* ─── ORDER DETAIL VIEW (API order) ─── *@ + var od = _selectedOrderDetail.Order; + var items = _selectedOrderDetail.Items ?? new(); +
+
+ + Chi tiết đơn hàng +
+
+
+
+ Mã đơn + @(od?.Id.ToString()[..8].ToUpper()) +
+
+ Ngày + @(od?.CreatedAt.ToString("dd/MM/yyyy — HH:mm:ss")) +
+
+ Trạng thái + @MapApiStatus(od?.Status) +
+
+ Thanh toán + @MapPaymentMethodLabel(od?.PaymentMethod) +
+
+
+ + + + + + + + + + + @foreach (var item in items) + { + + + + + + + } + +
Sản phẩmSLĐ.GiáT.Tiền
@(item.ProductName ?? "—")@item.Quantity@FormatPrice(item.UnitPrice)@FormatPrice(item.Subtotal)
+
+
+ TỔNG CỘNG + @FormatPrice(od?.TotalAmount ?? 0) +
+
+
+ +
+
+ } + else if (_selectedOrder != null) + { + @* ─── SESSION ORDER DETAIL VIEW ─── *@ + var so = _selectedOrder; +
+
+ + Chi tiết đơn hàng +
+
+
+
+ Mã đơn + @so.Id +
+
+ Thời gian + @so.Time +
+
+ Trạng thái + @so.Status +
+
+ Thanh toán + @so.Method +
+
+
+
@so.Items
+
+
+ TỔNG CỘNG + @FormatPrice(so.Total) +
+
+
+ +
+
+ } + else + { + @* ─── ORDER LIST ─── *@
@@ -273,10 +398,10 @@ }
- @if (_historyLoading) + @if (_historyLoading || _orderDetailLoading) {
-
Đang tải đơn hàng...
+
Đang tải...
} else @@ -302,7 +427,7 @@ @* EN: API orders from DB *@ @foreach (var order in FilteredApiOrders) { -
+
@order.Id.ToString()[..8].ToUpper() @@ -328,6 +453,7 @@ } }
+ }
break; @@ -446,6 +572,99 @@ }
break; + + case PosTab.Settings: + @* ═══ PAYMENT SETTINGS TAB ═══ *@ +
+
Cài đặt thanh toán
+ + @* ─── Card ─── *@ +
+
+
+ + Thẻ (Card) +
+
+ + +
+
+ @if (_settingsCardOpen && _payCardEnabled) + { +
+ + +
+ } +
+ + @* ─── QR ─── *@ +
+
+
+ + Mã QR +
+
+ + +
+
+ @if (_settingsQrOpen && _payQrEnabled) + { +
+ + + + + + +
+ } +
+ + @* ─── Transfer ─── *@ +
+
+
+ + Chuyển khoản +
+
+ + +
+
+ @if (_settingsTransferOpen && _payTransferEnabled) + { +
+ + + + + + +
+ } +
+ + + @if (!string.IsNullOrEmpty(_settingsSaveMsg)) + { +
@_settingsSaveMsg
+ } +
+ break; }
@@ -466,11 +685,16 @@ Dashboard +
@code { // ═══════════════ TAB SYSTEM ═══════════════ - private enum PosTab { Sale, History, Dashboard } + private enum PosTab { Sale, History, Dashboard, Settings } private PosTab _activeTab = PosTab.Sale; private async Task SwitchTab(PosTab tab) @@ -529,6 +753,8 @@ } catch { _loadError = true; } finally { _isLoading = false; } + + await LoadPaymentSettings(); } private void AddToCart(Product product) @@ -611,10 +837,10 @@ private string GetMethodLabel() => _selectedMethod switch { - "cash" => "💵 Tiền mặt", - "card" => "💳 Thẻ", - "qr" => "📱 Mã QR", - "transfer" => "🏦 Chuyển khoản", + "cash" => "Tiền mặt", + "card" => "Thẻ", + "qr" => "Mã QR", + "transfer" => "Chuyển khoản", _ => "Thanh toán" }; @@ -764,6 +990,8 @@ private SessionOrder? _selectedOrder; private bool _historyLoading; private bool _historyLoaded; + private PosDataService.OrderDetailResponse? _selectedOrderDetail; + private bool _orderDetailLoading; // EN: Session orders created during this POS session (in-memory, real-time) // VI: Đơn hàng tạo trong phiên POS này (in-memory, real-time) @@ -839,6 +1067,102 @@ _ => method ?? "Tiền mặt" }; + private async Task ViewOrderDetail(Guid orderId) + { + _orderDetailLoading = true; + StateHasChanged(); + try { _selectedOrderDetail = await DataService.GetOrderDetailAsync(orderId, ShopId); } + catch { _selectedOrderDetail = null; } + _orderDetailLoading = false; + StateHasChanged(); + } + + private void CloseOrderDetail() + { + _selectedOrderDetail = null; + _selectedOrder = null; + } + + private async Task PrintOrderDetail() + { + if (_selectedOrderDetail?.Order == null) return; + var od = _selectedOrderDetail.Order; + var items = _selectedOrderDetail.Items ?? new(); + var payLabel = MapPaymentMethodLabel(od.PaymentMethod); + + var sb = new System.Text.StringBuilder(); + foreach (var item in items) + { + sb.AppendLine($"{System.Net.WebUtility.HtmlEncode(item.ProductName ?? "—")}"); + sb.AppendLine($"{item.Quantity}"); + sb.AppendLine($"{item.UnitPrice:N0}"); + sb.AppendLine($"{item.Subtotal:N0}"); + } + + var receiptHtml = "" + + $"Hóa đơn - {od.Id.ToString()[..8].ToUpper()}" + + "" + + "
GoodGo POS
" + + "
Hệ thống quản lý bán hàng thông minh
" + + "
" + + $"
Mã đơn: {od.Id.ToString()[..8].ToUpper()}
" + + $"
Ngày: {od.CreatedAt:dd/MM/yyyy} — {od.CreatedAt:HH:mm:ss}
" + + $"
Thanh toán: {payLabel}
" + + "
" + + "" + + sb.ToString() + + "
Sản phẩmSLĐ.GiáT.Tiền
" + + $"
TỔNG CỘNG{od.TotalAmount:N0}₫
" + + "
" + + "
Cảm ơn quý khách! Hẹn gặp lại
" + + "
Powered by GoodGo Platform
" + + "" + + ""; + + await JS.InvokeVoidAsync("printPosReceipt", receiptHtml); + } + + private async Task PrintSessionOrderDetail() + { + if (_selectedOrder == null) return; + var so = _selectedOrder; + + var receiptHtml = "" + + $"Hóa đơn - {System.Net.WebUtility.HtmlEncode(so.Id)}" + + "" + + "
GoodGo POS
" + + "
Hệ thống quản lý bán hàng thông minh
" + + "
" + + $"
Mã đơn: {System.Net.WebUtility.HtmlEncode(so.Id)}
" + + $"
Thời gian: {System.Net.WebUtility.HtmlEncode(so.Time)}
" + + $"
Thanh toán: {System.Net.WebUtility.HtmlEncode(so.Method)}
" + + "
" + + $"
{System.Net.WebUtility.HtmlEncode(so.Items)}
" + + "
" + + $"
TỔNG CỘNG{so.Total:N0}₫
" + + "
" + + "
Cảm ơn quý khách! Hẹn gặp lại
" + + "
Powered by GoodGo Platform
" + + "" + + ""; + + await JS.InvokeVoidAsync("printPosReceipt", receiptHtml); + } + // ═══════════════ DASHBOARD TAB — API-driven ═══════════════ private bool _dashLoading; private bool _dashLoaded; @@ -890,6 +1214,74 @@ StateHasChanged(); } + // ═══════════════ PAYMENT SETTINGS ═══════════════ + private bool _payCardEnabled = true; + private bool _payQrEnabled = true; + private bool _payTransferEnabled = true; + private string _cardTerminalId = ""; + private string _qrBankName = ""; + private string _qrAccountNumber = ""; + private string _qrAccountName = ""; + private string _transferBankName = ""; + private string _transferAccountNumber = ""; + private string _transferAccountName = ""; + private bool _settingsCardOpen; + private bool _settingsQrOpen; + private bool _settingsTransferOpen; + private string? _settingsSaveMsg; + + private void ToggleCard() => _payCardEnabled = !_payCardEnabled; + private void ToggleQr() => _payQrEnabled = !_payQrEnabled; + private void ToggleTransfer() => _payTransferEnabled = !_payTransferEnabled; + + private async Task LoadPaymentSettings() + { + try + { + var json = await JS.InvokeAsync("localStorage.getItem", "pos_payment_settings"); + if (!string.IsNullOrEmpty(json)) + { + var doc = System.Text.Json.JsonDocument.Parse(json); + var r = doc.RootElement; + if (r.TryGetProperty("cardEnabled", out var ce)) _payCardEnabled = ce.GetBoolean(); + if (r.TryGetProperty("qrEnabled", out var qe)) _payQrEnabled = qe.GetBoolean(); + if (r.TryGetProperty("transferEnabled", out var te)) _payTransferEnabled = te.GetBoolean(); + if (r.TryGetProperty("cardTerminalId", out var ct)) _cardTerminalId = ct.GetString() ?? ""; + if (r.TryGetProperty("qrBankName", out var qb)) _qrBankName = qb.GetString() ?? ""; + if (r.TryGetProperty("qrAccountNumber", out var qa)) _qrAccountNumber = qa.GetString() ?? ""; + if (r.TryGetProperty("qrAccountName", out var qn)) _qrAccountName = qn.GetString() ?? ""; + if (r.TryGetProperty("transferBankName", out var tb)) _transferBankName = tb.GetString() ?? ""; + if (r.TryGetProperty("transferAccountNumber", out var ta)) _transferAccountNumber = ta.GetString() ?? ""; + if (r.TryGetProperty("transferAccountName", out var tn)) _transferAccountName = tn.GetString() ?? ""; + } + } + catch { /* first load — no settings yet */ } + } + + private async Task SavePaymentSettings() + { + var settings = new + { + cardEnabled = _payCardEnabled, + qrEnabled = _payQrEnabled, + transferEnabled = _payTransferEnabled, + cardTerminalId = _cardTerminalId, + qrBankName = _qrBankName, + qrAccountNumber = _qrAccountNumber, + qrAccountName = _qrAccountName, + transferBankName = _transferBankName, + transferAccountNumber = _transferAccountNumber, + transferAccountName = _transferAccountName + }; + var json = System.Text.Json.JsonSerializer.Serialize(settings); + await JS.InvokeVoidAsync("localStorage.setItem", "pos_payment_settings", json); + _settingsSaveMsg = "Đã lưu cài đặt!"; + StateHasChanged(); + await Task.Delay(2000); + _settingsSaveMsg = null; + StateHasChanged(); + } + // ═══════════════ RECORDS ═══════════════ private record Product(Guid Id, string Name, decimal Price, string Category); private class CartItem(Guid productId, string name, decimal price) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/pos.css b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/pos.css index 2233ef47..12f03949 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/pos.css +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/pos.css @@ -892,4 +892,44 @@ .pos-product-panel--dimmed { opacity: 0.4; pointer-events: none; +} + +/* Toggle switch */ +.pos-toggle { + position: relative; + width: 40px; + height: 22px; + border-radius: 11px; + border: none; + background: var(--pos-border-default, #444); + cursor: pointer; + transition: background 0.2s; + padding: 0; +} +.pos-toggle--on { background: var(--pos-orange-primary, #ff5c00); } +.pos-toggle__knob { + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + transition: transform 0.2s; +} +.pos-toggle--on .pos-toggle__knob { transform: translateX(18px); } + +/* Settings input */ +.pos-settings-input { + width: 100%; + padding: 10px 12px; + border-radius: var(--pos-radius, 8px); + border: 1px solid var(--pos-border-subtle, #333); + background: var(--pos-bg-interactive, #1a1a2e); + color: var(--pos-text-primary, #fff); + font-size: 13px; + outline: none; +} +.pos-settings-input:focus { + border-color: var(--pos-orange-primary, #ff5c00); } \ No newline at end of file