feat(pos-cafe): implement payment method settings, add detailed order history view, and update payment icons to Lucide
This commit is contained in:
@@ -126,21 +126,30 @@
|
||||
</div>
|
||||
<div class="pos-payment-methods">
|
||||
<button class="pos-payment-method-btn" @onclick='() => SelectPaymentMethod("cash")'>
|
||||
<span class="pos-payment-method-btn__icon">💵</span>
|
||||
<span class="pos-payment-method-btn__icon"><i data-lucide="wallet" style="width:28px;height:28px;"></i></span>
|
||||
<span class="pos-payment-method-btn__label">Tiền mặt</span>
|
||||
</button>
|
||||
@if (_payCardEnabled)
|
||||
{
|
||||
<button class="pos-payment-method-btn" @onclick='() => SelectPaymentMethod("card")'>
|
||||
<span class="pos-payment-method-btn__icon">💳</span>
|
||||
<span class="pos-payment-method-btn__icon"><i data-lucide="credit-card" style="width:28px;height:28px;"></i></span>
|
||||
<span class="pos-payment-method-btn__label">Thẻ</span>
|
||||
</button>
|
||||
}
|
||||
@if (_payQrEnabled)
|
||||
{
|
||||
<button class="pos-payment-method-btn" @onclick='() => SelectPaymentMethod("qr")'>
|
||||
<span class="pos-payment-method-btn__icon">📱</span>
|
||||
<span class="pos-payment-method-btn__icon"><i data-lucide="smartphone" style="width:28px;height:28px;"></i></span>
|
||||
<span class="pos-payment-method-btn__label">Mã QR</span>
|
||||
</button>
|
||||
}
|
||||
@if (_payTransferEnabled)
|
||||
{
|
||||
<button class="pos-payment-method-btn" @onclick='() => SelectPaymentMethod("transfer")'>
|
||||
<span class="pos-payment-method-btn__icon">🏦</span>
|
||||
<span class="pos-payment-method-btn__icon"><i data-lucide="building-2" style="width:28px;height:28px;"></i></span>
|
||||
<span class="pos-payment-method-btn__label">Chuyển khoản</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -152,7 +161,7 @@
|
||||
<button class="pos-payment-header__back" @onclick="() => _paymentStep = PayStep.MethodSelect">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
<span style="font-weight:600;">💵 Tiền mặt</span>
|
||||
<span style="font-weight:600;display:flex;align-items:center;gap:6px;"><i data-lucide="wallet" style="width:16px;height:16px;"></i> Tiền mặt</span>
|
||||
<span style="margin-left:auto;font-size:16px;font-weight:700;color:var(--pos-orange-primary);">@FormatPrice(FinalTotal)</span>
|
||||
</div>
|
||||
<div class="pos-payment-amount-section">
|
||||
@@ -215,12 +224,12 @@
|
||||
}
|
||||
else if (_selectedMethod == "card")
|
||||
{
|
||||
<div style="font-size:48px;">💳</div>
|
||||
<div><i data-lucide="credit-card" style="width:48px;height:48px;color:var(--pos-text-secondary);"></i></div>
|
||||
<div style="font-size:14px;color:var(--pos-text-secondary);">Chạm, quẹt hoặc cắm thẻ</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="font-size:48px;">🏦</div>
|
||||
<div><i data-lucide="building-2" style="width:48px;height:48px;color:var(--pos-text-secondary);"></i></div>
|
||||
<div style="font-size:14px;color:var(--pos-text-secondary);">Xác nhận đã nhận chuyển khoản</div>
|
||||
}
|
||||
</div>
|
||||
@@ -261,6 +270,122 @@
|
||||
case PosTab.History:
|
||||
@* ═══ ORDER HISTORY TAB ═══ *@
|
||||
<div class="pos-history" style="width:100%;">
|
||||
@if (_selectedOrderDetail != null)
|
||||
{
|
||||
@* ─── ORDER DETAIL VIEW (API order) ─── *@
|
||||
var od = _selectedOrderDetail.Order;
|
||||
var items = _selectedOrderDetail.Items ?? new();
|
||||
<div style="display:flex;flex-direction:column;height:100%;">
|
||||
<div class="pos-payment-header">
|
||||
<button class="pos-payment-header__back" @onclick="CloseOrderDetail">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
<span style="font-weight:600;">Chi tiết đơn hàng</span>
|
||||
</div>
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Mã đơn</span>
|
||||
<span style="font-weight:600;">@(od?.Id.ToString()[..8].ToUpper())</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Ngày</span>
|
||||
<span>@(od?.CreatedAt.ToString("dd/MM/yyyy — HH:mm:ss"))</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Trạng thái</span>
|
||||
<span style="color:var(--pos-success);font-weight:600;">@MapApiStatus(od?.Status)</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Thanh toán</span>
|
||||
<span>@MapPaymentMethodLabel(od?.PaymentMethod)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border-top:1px dashed var(--pos-border-subtle);margin-bottom:12px;"></div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
||||
<thead>
|
||||
<tr style="color:var(--pos-text-tertiary);font-size:11px;text-transform:uppercase;">
|
||||
<th style="text-align:left;padding:6px 0;">Sản phẩm</th>
|
||||
<th style="text-align:center;padding:6px 4px;width:40px;">SL</th>
|
||||
<th style="text-align:right;padding:6px 0;width:80px;">Đ.Giá</th>
|
||||
<th style="text-align:right;padding:6px 0;width:90px;">T.Tiền</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in items)
|
||||
{
|
||||
<tr style="border-top:1px solid var(--pos-border-subtle);">
|
||||
<td style="padding:8px 0;">@(item.ProductName ?? "—")</td>
|
||||
<td style="text-align:center;padding:8px 4px;">@item.Quantity</td>
|
||||
<td style="text-align:right;padding:8px 0;">@FormatPrice(item.UnitPrice)</td>
|
||||
<td style="text-align:right;padding:8px 0;font-weight:600;">@FormatPrice(item.Subtotal)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="border-top:1px dashed var(--pos-border-subtle);margin:12px 0;"></div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:16px;font-weight:700;">
|
||||
<span>TỔNG CỘNG</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(od?.TotalAmount ?? 0)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:16px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
|
||||
<button style="flex:1;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:13px;font-weight:600;"
|
||||
@onclick="PrintOrderDetail">
|
||||
<i data-lucide="printer" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"></i>In hóa đơn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (_selectedOrder != null)
|
||||
{
|
||||
@* ─── SESSION ORDER DETAIL VIEW ─── *@
|
||||
var so = _selectedOrder;
|
||||
<div style="display:flex;flex-direction:column;height:100%;">
|
||||
<div class="pos-payment-header">
|
||||
<button class="pos-payment-header__back" @onclick="CloseOrderDetail">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
<span style="font-weight:600;">Chi tiết đơn hàng</span>
|
||||
</div>
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Mã đơn</span>
|
||||
<span style="font-weight:600;">@so.Id</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Thời gian</span>
|
||||
<span>@so.Time</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Trạng thái</span>
|
||||
<span style="color:var(--pos-success);font-weight:600;">@so.Status</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-tertiary);">Thanh toán</span>
|
||||
<span>@so.Method</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border-top:1px dashed var(--pos-border-subtle);margin-bottom:12px;"></div>
|
||||
<div style="font-size:13px;color:var(--pos-text-secondary);margin-bottom:12px;">@so.Items</div>
|
||||
<div style="border-top:1px dashed var(--pos-border-subtle);margin:12px 0;"></div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:16px;font-weight:700;">
|
||||
<span>TỔNG CỘNG</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(so.Total)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:16px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
|
||||
<button style="flex:1;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:13px;font-weight:600;"
|
||||
@onclick="PrintSessionOrderDetail">
|
||||
<i data-lucide="printer" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"></i>In hóa đơn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* ─── ORDER LIST ─── *@
|
||||
<div class="pos-history__toolbar">
|
||||
<input class="pos-history__search" placeholder="Tìm mã đơn, tên khách..."
|
||||
@bind="_historySearch" @bind:event="oninput" />
|
||||
@@ -273,10 +398,10 @@
|
||||
}
|
||||
</div>
|
||||
<div class="pos-history__list">
|
||||
@if (_historyLoading)
|
||||
@if (_historyLoading || _orderDetailLoading)
|
||||
{
|
||||
<div style="display:flex;align-items:center;justify-content:center;padding:60px 0;color:var(--pos-text-tertiary);">
|
||||
<div style="font-size:14px;">Đang tải đơn hàng...</div>
|
||||
<div style="font-size:14px;">Đang tải...</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -302,7 +427,7 @@
|
||||
@* EN: API orders from DB *@
|
||||
@foreach (var order in FilteredApiOrders)
|
||||
{
|
||||
<div class="pos-history__card">
|
||||
<div class="pos-history__card" @onclick="() => ViewOrderDetail(order.Id)">
|
||||
<div class="pos-history__card-header">
|
||||
<span class="pos-history__order-id">@order.Id.ToString()[..8].ToUpper()</span>
|
||||
<span class="pos-history__status @(order.Status == "Completed" || order.Status == "Delivered" ? "pos-history__status--completed" : "pos-history__status--refunded")">
|
||||
@@ -328,6 +453,7 @@
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
break;
|
||||
|
||||
@@ -446,6 +572,99 @@
|
||||
}
|
||||
</div>
|
||||
break;
|
||||
|
||||
case PosTab.Settings:
|
||||
@* ═══ PAYMENT SETTINGS TAB ═══ *@
|
||||
<div style="width:100%;max-width:560px;margin:0 auto;padding:16px;">
|
||||
<div style="font-size:18px;font-weight:700;color:var(--pos-text-primary);margin-bottom:16px;">Cài đặt thanh toán</div>
|
||||
|
||||
@* ─── Card ─── *@
|
||||
<div style="background:var(--pos-bg-card);border:1px solid var(--pos-border-subtle);border-radius:var(--pos-radius);margin-bottom:12px;overflow:hidden;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;cursor:pointer;" @onclick="() => _settingsCardOpen = !_settingsCardOpen">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<i data-lucide="credit-card" style="width:20px;height:20px;color:var(--pos-orange-primary);"></i>
|
||||
<span style="font-weight:600;font-size:14px;">Thẻ (Card)</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button class="pos-toggle @(_payCardEnabled ? "pos-toggle--on" : "")" @onclick="ToggleCard" @onclick:stopPropagation="true">
|
||||
<span class="pos-toggle__knob"></span>
|
||||
</button>
|
||||
<i data-lucide="@(_settingsCardOpen ? "chevron-up" : "chevron-down")" style="width:16px;height:16px;color:var(--pos-text-tertiary);"></i>
|
||||
</div>
|
||||
</div>
|
||||
@if (_settingsCardOpen && _payCardEnabled)
|
||||
{
|
||||
<div style="padding:0 16px 14px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Terminal ID</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="Nhập mã terminal..." @bind="_cardTerminalId" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ─── QR ─── *@
|
||||
<div style="background:var(--pos-bg-card);border:1px solid var(--pos-border-subtle);border-radius:var(--pos-radius);margin-bottom:12px;overflow:hidden;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;cursor:pointer;" @onclick="() => _settingsQrOpen = !_settingsQrOpen">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<i data-lucide="smartphone" style="width:20px;height:20px;color:var(--pos-orange-primary);"></i>
|
||||
<span style="font-weight:600;font-size:14px;">Mã QR</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button class="pos-toggle @(_payQrEnabled ? "pos-toggle--on" : "")" @onclick="ToggleQr" @onclick:stopPropagation="true">
|
||||
<span class="pos-toggle__knob"></span>
|
||||
</button>
|
||||
<i data-lucide="@(_settingsQrOpen ? "chevron-up" : "chevron-down")" style="width:16px;height:16px;color:var(--pos-text-tertiary);"></i>
|
||||
</div>
|
||||
</div>
|
||||
@if (_settingsQrOpen && _payQrEnabled)
|
||||
{
|
||||
<div style="padding:0 16px 14px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Ngân hàng</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="VD: Vietcombank, MB Bank..." @bind="_qrBankName" />
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Số tài khoản</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="Nhập số tài khoản..." @bind="_qrAccountNumber" />
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Tên chủ tài khoản</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="Nhập tên chủ TK..." @bind="_qrAccountName" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ─── Transfer ─── *@
|
||||
<div style="background:var(--pos-bg-card);border:1px solid var(--pos-border-subtle);border-radius:var(--pos-radius);margin-bottom:12px;overflow:hidden;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;cursor:pointer;" @onclick="() => _settingsTransferOpen = !_settingsTransferOpen">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<i data-lucide="building-2" style="width:20px;height:20px;color:var(--pos-orange-primary);"></i>
|
||||
<span style="font-weight:600;font-size:14px;">Chuyển khoản</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button class="pos-toggle @(_payTransferEnabled ? "pos-toggle--on" : "")" @onclick="ToggleTransfer" @onclick:stopPropagation="true">
|
||||
<span class="pos-toggle__knob"></span>
|
||||
</button>
|
||||
<i data-lucide="@(_settingsTransferOpen ? "chevron-up" : "chevron-down")" style="width:16px;height:16px;color:var(--pos-text-tertiary);"></i>
|
||||
</div>
|
||||
</div>
|
||||
@if (_settingsTransferOpen && _payTransferEnabled)
|
||||
{
|
||||
<div style="padding:0 16px 14px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Ngân hàng</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="VD: Vietcombank, MB Bank..." @bind="_transferBankName" />
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Số tài khoản</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="Nhập số tài khoản..." @bind="_transferAccountNumber" />
|
||||
<label style="font-size:12px;color:var(--pos-text-tertiary);display:block;margin:12px 0 4px;">Tên chủ tài khoản</label>
|
||||
<input type="text" class="pos-settings-input" placeholder="Nhập tên chủ TK..." @bind="_transferAccountName" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button style="width:100%;padding:12px;border-radius:var(--pos-radius);border:none;background:var(--pos-orange-primary);color:#fff;font-size:14px;font-weight:600;cursor:pointer;margin-top:8px;"
|
||||
@onclick="SavePaymentSettings">
|
||||
<i data-lucide="save" style="width:16px;height:16px;vertical-align:middle;margin-right:6px;"></i>Lưu cài đặt
|
||||
</button>
|
||||
@if (!string.IsNullOrEmpty(_settingsSaveMsg))
|
||||
{
|
||||
<div style="text-align:center;font-size:13px;color:var(--pos-success);margin-top:8px;">@_settingsSaveMsg</div>
|
||||
}
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -466,11 +685,16 @@
|
||||
<i data-lucide="bar-chart-3" class="pos-bottom-nav__icon"></i>
|
||||
<span>Dashboard</span>
|
||||
</button>
|
||||
<button class="pos-bottom-nav__tab @(_activeTab == PosTab.Settings ? "pos-bottom-nav__tab--active" : "")"
|
||||
@onclick="() => SwitchTab(PosTab.Settings)">
|
||||
<i data-lucide="settings" class="pos-bottom-nav__icon"></i>
|
||||
<span>Cài đặt</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@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($"<tr><td style='text-align:left;padding:3px 0;'>{System.Net.WebUtility.HtmlEncode(item.ProductName ?? "—")}</td>");
|
||||
sb.AppendLine($"<td style='text-align:center;padding:3px 4px;'>{item.Quantity}</td>");
|
||||
sb.AppendLine($"<td style='text-align:right;padding:3px 0;'>{item.UnitPrice:N0}</td>");
|
||||
sb.AppendLine($"<td style='text-align:right;padding:3px 0;font-weight:600;'>{item.Subtotal:N0}</td></tr>");
|
||||
}
|
||||
|
||||
var receiptHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>" +
|
||||
$"<title>Hóa đơn - {od.Id.ToString()[..8].ToUpper()}</title>" +
|
||||
"<style>" +
|
||||
"@page { margin: 4mm; size: 80mm auto; }" +
|
||||
"body { font-family: 'Courier New', monospace; font-size: 12px; width: 72mm; margin: 0 auto; color: #000; }" +
|
||||
".c { text-align: center; } .b { font-weight: bold; }" +
|
||||
".d { border-top: 1px dashed #000; margin: 6px 0; }" +
|
||||
"table { width: 100%; border-collapse: collapse; }" +
|
||||
"th { text-align: left; font-size: 11px; border-bottom: 1px solid #000; padding: 2px 0; }" +
|
||||
".f { font-size: 10px; text-align: center; margin-top: 8px; color: #555; }" +
|
||||
"</style></head><body>" +
|
||||
"<div class='c b' style='font-size:16px;'>GoodGo POS</div>" +
|
||||
"<div class='c' style='font-size:10px;margin-bottom:4px;'>Hệ thống quản lý bán hàng thông minh</div>" +
|
||||
"<div class='d'></div>" +
|
||||
$"<div><b>Mã đơn:</b> {od.Id.ToString()[..8].ToUpper()}</div>" +
|
||||
$"<div><b>Ngày:</b> {od.CreatedAt:dd/MM/yyyy} — {od.CreatedAt:HH:mm:ss}</div>" +
|
||||
$"<div><b>Thanh toán:</b> {payLabel}</div>" +
|
||||
"<div class='d'></div>" +
|
||||
"<table><tr><th>Sản phẩm</th><th style='text-align:center;'>SL</th><th style='text-align:right;'>Đ.Giá</th><th style='text-align:right;'>T.Tiền</th></tr>" +
|
||||
sb.ToString() +
|
||||
"</table><div class='d'></div>" +
|
||||
$"<div style='display:flex;justify-content:space-between;font-size:14px;font-weight:bold;'><span>TỔNG CỘNG</span><span>{od.TotalAmount:N0}₫</span></div>" +
|
||||
"<div class='d'></div>" +
|
||||
"<div class='f'>Cảm ơn quý khách! Hẹn gặp lại</div>" +
|
||||
"<div class='f'>Powered by GoodGo Platform</div>" +
|
||||
"<script>window.onload=function(){window.print();window.onafterprint=function(){window.close();}}</script>" +
|
||||
"</body></html>";
|
||||
|
||||
await JS.InvokeVoidAsync("printPosReceipt", receiptHtml);
|
||||
}
|
||||
|
||||
private async Task PrintSessionOrderDetail()
|
||||
{
|
||||
if (_selectedOrder == null) return;
|
||||
var so = _selectedOrder;
|
||||
|
||||
var receiptHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>" +
|
||||
$"<title>Hóa đơn - {System.Net.WebUtility.HtmlEncode(so.Id)}</title>" +
|
||||
"<style>" +
|
||||
"@page { margin: 4mm; size: 80mm auto; }" +
|
||||
"body { font-family: 'Courier New', monospace; font-size: 12px; width: 72mm; margin: 0 auto; color: #000; }" +
|
||||
".c { text-align: center; } .b { font-weight: bold; }" +
|
||||
".d { border-top: 1px dashed #000; margin: 6px 0; }" +
|
||||
".f { font-size: 10px; text-align: center; margin-top: 8px; color: #555; }" +
|
||||
"</style></head><body>" +
|
||||
"<div class='c b' style='font-size:16px;'>GoodGo POS</div>" +
|
||||
"<div class='c' style='font-size:10px;margin-bottom:4px;'>Hệ thống quản lý bán hàng thông minh</div>" +
|
||||
"<div class='d'></div>" +
|
||||
$"<div><b>Mã đơn:</b> {System.Net.WebUtility.HtmlEncode(so.Id)}</div>" +
|
||||
$"<div><b>Thời gian:</b> {System.Net.WebUtility.HtmlEncode(so.Time)}</div>" +
|
||||
$"<div><b>Thanh toán:</b> {System.Net.WebUtility.HtmlEncode(so.Method)}</div>" +
|
||||
"<div class='d'></div>" +
|
||||
$"<div style='font-size:12px;'>{System.Net.WebUtility.HtmlEncode(so.Items)}</div>" +
|
||||
"<div class='d'></div>" +
|
||||
$"<div style='display:flex;justify-content:space-between;font-size:14px;font-weight:bold;'><span>TỔNG CỘNG</span><span>{so.Total:N0}₫</span></div>" +
|
||||
"<div class='d'></div>" +
|
||||
"<div class='f'>Cảm ơn quý khách! Hẹn gặp lại</div>" +
|
||||
"<div class='f'>Powered by GoodGo Platform</div>" +
|
||||
"<script>window.onload=function(){window.print();window.onafterprint=function(){window.close();}}</script>" +
|
||||
"</body></html>";
|
||||
|
||||
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<string?>("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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user