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();
+
+
+
+
+
+ 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)
+
+
+
+
+
+
+ | Sản phẩm |
+ SL |
+ Đ.Giá |
+ T.Tiền |
+
+
+
+ @foreach (var item in items)
+ {
+
+ | @(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;
+
+
+
+
+
+ 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)
{
-
+
ViewOrderDetail(order.Id)">
+ }
break;
@@ -446,6 +572,99 @@
}
break;
+
+ case PosTab.Settings:
+ @* ═══ PAYMENT SETTINGS TAB ═══ *@
+
+
Cài đặt thanh toán
+
+ @* ─── Card ─── *@
+
+
_settingsCardOpen = !_settingsCardOpen">
+
+
+ Thẻ (Card)
+
+
+
+
+
+
+ @if (_settingsCardOpen && _payCardEnabled)
+ {
+
+
+
+
+ }
+
+
+ @* ─── QR ─── *@
+
+
_settingsQrOpen = !_settingsQrOpen">
+
+
+ Mã QR
+
+
+
+
+
+
+ @if (_settingsQrOpen && _payQrEnabled)
+ {
+
+
+
+
+
+
+
+
+ }
+
+
+ @* ─── Transfer ─── *@
+
+
_settingsTransferOpen = !_settingsTransferOpen">
+
+
+ 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}
" +
+ "" +
+ "| Sản phẩm | SL | Đ.Giá | T.Tiền |
" +
+ sb.ToString() +
+ "
" +
+ $"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