feat(pos): add 8 shared dialog/screen Razor files

Add POS dialog components in Shared/Dialogs/:
- VoidRefund.razor: Void/refund dialog with order lookup, type selection, reason, manager PIN
- OrderEdit.razor: Edit existing order with qty controls, add items, discount, notes
- OrderCancel.razor: Cancel order confirmation with reason dropdown, refund warning
- SplitBill.razor: Split bill with equal/by-item/custom modes
- StockIn.razor: Quick stock-in with supplier, lot, expiry, recent log
- StockOut.razor: Stock-out with current stock display, reason selection
- StockTransfer.razor: Transfer between branches with product list
- PriceCheck.razor: Price check with hero price, promotions, price history

All files use @layout PosLayout, @inherits PosBase, bilingual comments,
CSS variables, Vietnamese demo data with VND prices.

Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-02-26 15:50:10 +00:00
parent 9bfab575c6
commit d3cb537e3e
8 changed files with 1494 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
@*
EN: Cancel Order Confirmation — Order summary, reason dropdown, notes, refund warning, cancel/keep buttons.
VI: Xác nhận hủy đơn — Tổng quan đơn, lý do hủy, ghi chú, cảnh báo hoàn tiền, nút hủy/giữ đơn.
*@
@page "/pos/dialog/order-cancel"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:520px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:90vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(239,68,68,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="ban" style="width:20px;height:20px;color:var(--pos-danger);"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Hủy đơn hàng</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Đơn #DH2024-0589</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ ORDER SUMMARY / TÓM TẮT ĐƠN HÀNG ═══ *@
<div style="background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:16px;margin-bottom:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<span style="font-size:14px;font-weight:600;">Đơn #DH2024-0589</span>
<span style="font-size:12px;color:var(--pos-text-tertiary);">Bàn 8 · 16:45</span>
</div>
@foreach (var item in _orderItems)
{
<div style="display:flex;justify-content:space-between;padding:5px 0;font-size:13px;border-bottom:1px solid var(--pos-border-subtle);">
<span style="color:var(--pos-text-secondary);">x@(item.Qty) @item.Name</span>
<span style="font-weight:500;">@FormatPrice(item.Price * item.Qty)</span>
</div>
}
<div style="display:flex;justify-content:space-between;padding-top:10px;font-size:15px;font-weight:700;">
<span>Tổng</span>
<span style="color:var(--pos-orange-primary);">@FormatPrice(_orderItems.Sum(i => i.Price * i.Qty))</span>
</div>
</div>
@* ═══ REFUND WARNING / CẢNH BÁO HOÀN TIỀN ═══ *@
@if (_isPaid)
{
<div style="display:flex;gap:10px;padding:14px;border-radius:var(--pos-radius);
background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.3);margin-bottom:20px;">
<i data-lucide="alert-triangle" style="width:20px;height:20px;color:var(--pos-warning);flex-shrink:0;margin-top:2px;"></i>
<div>
<div style="font-size:13px;font-weight:600;color:var(--pos-warning);">Đơn hàng đã thanh toán</div>
<div style="font-size:12px;color:var(--pos-text-secondary);margin-top:4px;">
Hủy đơn này sẽ phát sinh hoàn tiền @FormatPrice(_orderItems.Sum(i => i.Price * i.Qty)) cho khách hàng.
Vui lòng xác nhận với quản lý trước khi tiếp tục.
</div>
</div>
</div>
}
@* ═══ CANCELLATION REASON / LÝ DO HỦY ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">
Lý do hủy <span style="color:var(--pos-danger);">*</span>
</div>
<select @bind="_selectedReason"
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;appearance:auto;">
<option value="">— Chọn lý do —</option>
@foreach (var reason in _reasons)
{
<option value="@reason">@reason</option>
}
</select>
</div>
@* ═══ NOTES / GHI CHÚ ═══ *@
<div style="margin-bottom:16px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Ghi chú thêm</div>
<textarea @bind="_note" rows="3" placeholder="Nhập chi tiết lý do hủy (nếu cần)..."
style="width:100%;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;resize:vertical;box-sizing:border-box;"></textarea>
</div>
</div>
@* ═══ ACTIONS / HÀNH ĐỘNG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;font-weight:500;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="arrow-left" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"></i>
Giữ đơn hàng
</button>
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-danger);color:#fff;cursor:pointer;font-size:14px;font-weight:600;
opacity:@(string.IsNullOrEmpty(_selectedReason) ? "0.5" : "1");
pointer-events:@(string.IsNullOrEmpty(_selectedReason) ? "none" : "auto");"
@onclick="CancelOrder">
<i data-lucide="trash-2" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Hủy đơn hàng
</button>
</div>
</div>
</div>
@code {
// EN: Cancel state / VI: Trạng thái hủy
private string _selectedReason = "";
private string _note = "";
private bool _isPaid = true;
// EN: Cancellation reasons / VI: Lý do hủy
private readonly string[] _reasons =
{
"Khách hủy", "Hết nguyên liệu", "Sai đơn", "Quá lâu", "Khác"
};
// EN: Demo order items / VI: Danh sách món mẫu
private readonly List<CancelItem> _orderItems = new()
{
new("Cơm tấm sườn bì chả", 65_000, 2),
new("Canh chua cá lóc", 85_000, 1),
new("Chả giò", 40_000, 1),
new("Nước mía", 20_000, 3),
};
private void CancelOrder()
{
NavigateTo("");
}
private record CancelItem(string Name, decimal Price, int Qty);
}

View File

@@ -0,0 +1,205 @@
@*
EN: Edit Existing Order — Editable item list, quantity controls, add items, discount, notes, recalculated total.
VI: Chỉnh sửa đơn hàng — Danh sách món chỉnh sửa, điều khiển số lượng, thêm món, giảm giá, ghi chú, tổng tính lại.
*@
@page "/pos/dialog/order-edit"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:640px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:92vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(59,130,246,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="pencil" style="width:20px;height:20px;color:#3B82F6;"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Chỉnh sửa đơn hàng</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Bàn 5 · 15:20 · Đang phục vụ</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ ORDER INFO / THÔNG TIN ĐƠN HÀNG ═══ *@
<div style="display:flex;gap:16px;margin-bottom:20px;">
<div style="flex:1;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:12px;">
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Khách hàng</div>
<div style="font-size:14px;font-weight:600;">Bàn 5 — 4 khách</div>
</div>
<div style="flex:1;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:12px;">
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Nhân viên</div>
<div style="font-size:14px;font-weight:600;">Trần Thị B</div>
</div>
<div style="flex:1;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:12px;">
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Trạng thái</div>
<div style="font-size:14px;font-weight:600;color:var(--pos-success);">Đang phục vụ</div>
</div>
</div>
@* ═══ EDITABLE ITEM LIST / DANH SÁCH MÓN CHỈNH SỬA ═══ *@
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Danh sách món</div>
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:20px;">
@foreach (var item in _items)
{
<div style="display:flex;align-items:center;gap:12px;padding:12px;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);">
<div style="flex:1;">
<div style="font-size:14px;font-weight:500;">@item.Name</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">@FormatPrice(item.Price)</div>
</div>
<div style="display:flex;align-items:center;gap:6px;">
<button style="width:30px;height:30px;border-radius:6px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-elevated);color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
@onclick="() => item.Qty = Math.Max(0, item.Qty - 1)"></button>
<span style="min-width:28px;text-align:center;font-size:15px;font-weight:600;">@item.Qty</span>
<button style="width:30px;height:30px;border-radius:6px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-elevated);color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
@onclick="() => item.Qty++">+</button>
</div>
<span style="min-width:80px;text-align:right;font-size:14px;font-weight:600;">@FormatPrice(item.Price * item.Qty)</span>
<button style="background:none;border:none;color:var(--pos-danger);cursor:pointer;padding:4px;"
@onclick="() => _items.Remove(item)">
<i data-lucide="trash-2" style="width:16px;height:16px;"></i>
</button>
</div>
}
</div>
@* ═══ ADD ITEM / THÊM MÓN ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Thêm món</div>
<div style="display:flex;gap:8px;margin-bottom:10px;">
<input type="text" @bind="_searchTerm" placeholder="Tìm kiếm món..."
style="flex:1;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;" />
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
@foreach (var quick in _quickAddItems)
{
<button style="padding:8px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);cursor:pointer;font-size:12px;"
@onclick="() => AddItem(quick)">
<i data-lucide="plus" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;"></i>
@quick.Name — @FormatPrice(quick.Price)
</button>
}
</div>
</div>
@* ═══ DISCOUNT / GIẢM GIÁ ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Giảm giá</div>
<div style="display:flex;gap:10px;align-items:center;">
<div style="display:flex;gap:6px;">
<button style="padding:8px 16px;border-radius:var(--pos-radius);border:2px solid @(_discountType == "percent" ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_discountType == "percent" ? "rgba(255,92,0,.1)" : "var(--pos-bg-interactive)");color:var(--pos-text-primary);cursor:pointer;font-size:13px;"
@onclick='() => _discountType = "percent"'>%</button>
<button style="padding:8px 16px;border-radius:var(--pos-radius);border:2px solid @(_discountType == "fixed" ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_discountType == "fixed" ? "rgba(255,92,0,.1)" : "var(--pos-bg-interactive)");color:var(--pos-text-primary);cursor:pointer;font-size:13px;"
@onclick='() => _discountType = "fixed"'>₫</button>
</div>
<input type="number" @bind="_discountValue" placeholder="0"
style="width:120px;padding:10px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;" />
</div>
</div>
@* ═══ SPECIAL NOTE / GHI CHÚ ĐẶC BIỆT ═══ *@
<div style="margin-bottom:16px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Ghi chú đặc biệt</div>
<textarea @bind="_specialNote" rows="2" placeholder="VD: Không hành, thêm ớt..."
style="width:100%;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;resize:vertical;box-sizing:border-box;"></textarea>
</div>
</div>
@* ═══ FOOTER — TOTAL & SAVE / CUỐI — TỔNG & LƯU ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);">
<div style="display:flex;justify-content:space-between;font-size:13px;color:var(--pos-text-secondary);margin-bottom:6px;">
<span>Tạm tính</span><span>@FormatPrice(Subtotal)</span>
</div>
@if (DiscountAmount > 0)
{
<div style="display:flex;justify-content:space-between;font-size:13px;color:var(--pos-danger);margin-bottom:6px;">
<span>Giảm giá</span><span>-@FormatPrice(DiscountAmount)</span>
</div>
}
<div style="display:flex;justify-content:space-between;font-size:18px;font-weight:700;margin-bottom:14px;">
<span>Tổng cộng</span>
<span style="color:var(--pos-orange-primary);">@FormatPrice(Total)</span>
</div>
<div style="display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;"
@onclick="@(() => NavigateTo(""))">
Hủy
</button>
<button style="flex:2;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-orange-primary);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="SaveChanges">
<i data-lucide="save" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Lưu thay đổi
</button>
</div>
</div>
</div>
</div>
@code {
// EN: Edit state / VI: Trạng thái chỉnh sửa
private string _searchTerm = "";
private string _discountType = "percent";
private decimal _discountValue = 10;
private string _specialNote = "Không hành cho món Phở";
// EN: Demo order items for "Bàn 5" / VI: Danh sách món mẫu "Bàn 5"
private readonly List<EditableItem> _items = new()
{
new("Phở bò tái", 75_000, 2),
new("Cơm tấm sườn bì chả", 65_000, 1),
new("Gỏi cuốn tôm thịt", 45_000, 1),
new("Trà đá", 10_000, 4),
};
// EN: Quick-add suggestions / VI: Gợi ý thêm nhanh
private readonly List<QuickItem> _quickAddItems = new()
{
new("Chả giò", 40_000),
new("Bánh flan", 25_000),
new("Nước suối", 15_000),
new("Bia Sài Gòn", 25_000),
};
private decimal Subtotal => _items.Where(i => i.Qty > 0).Sum(i => i.Price * i.Qty);
private decimal DiscountAmount => _discountType == "percent"
? Math.Round(Subtotal * _discountValue / 100)
: Math.Min(_discountValue, Subtotal);
private decimal Total => Math.Max(0, Subtotal - DiscountAmount);
private void AddItem(QuickItem quick)
{
var existing = _items.FirstOrDefault(i => i.Name == quick.Name);
if (existing != null)
existing.Qty++;
else
_items.Add(new EditableItem(quick.Name, quick.Price, 1));
}
private void SaveChanges() => NavigateTo("");
private class EditableItem(string name, decimal price, int qty)
{
public string Name { get; set; } = name;
public decimal Price { get; set; } = price;
public int Qty { get; set; } = qty;
}
private record QuickItem(string Name, decimal Price);
}

View File

@@ -0,0 +1,177 @@
@*
EN: Price Check — Barcode/SKU input, large price display, product details, promotions, price history.
VI: Kiểm tra giá — Nhập mã vạch/SKU, hiển thị giá lớn, chi tiết sản phẩm, khuyến mãi, lịch sử giá.
*@
@page "/pos/dialog/price-check"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:520px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:90vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(255,92,0,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="tag" style="width:20px;height:20px;color:var(--pos-orange-primary);"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Kiểm tra giá</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Tra cứu giá sản phẩm</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ SEARCH INPUT / Ô TÌM KIẾM ═══ *@
<div style="display:flex;gap:8px;margin-bottom:24px;">
<input type="text" @bind="_searchInput" placeholder="Nhập mã vạch hoặc SKU..."
style="flex:1;padding:14px 16px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:15px;" />
<button style="padding:14px;border-radius:var(--pos-radius);background:var(--pos-bg-interactive);
border:1px solid var(--pos-border-default);color:var(--pos-text-primary);cursor:pointer;"
@onclick="SearchProduct">
<i data-lucide="scan-barcode" style="width:20px;height:20px;"></i>
</button>
<button style="padding:14px 20px;border-radius:var(--pos-radius);background:var(--pos-orange-primary);
border:none;color:#fff;cursor:pointer;font-weight:600;font-size:14px;"
@onclick="SearchProduct">
<i data-lucide="search" style="width:16px;height:16px;vertical-align:middle;"></i>
</button>
</div>
@if (_productFound)
{
@* ═══ HERO PRICE / GIÁ NỔI BẬT ═══ *@
<div style="text-align:center;margin-bottom:24px;padding:24px;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);">
<div style="font-size:16px;font-weight:600;margin-bottom:8px;">@_productName</div>
@if (_hasPromotion)
{
<div style="font-size:16px;color:var(--pos-text-tertiary);text-decoration:line-through;margin-bottom:4px;">
@FormatPrice(_originalPrice)
</div>
}
<div style="font-size:48px;font-weight:800;color:var(--pos-orange-primary);line-height:1;">
@FormatPrice(_currentPrice)
</div>
@if (_hasPromotion)
{
<div style="display:inline-block;margin-top:10px;padding:4px 12px;border-radius:6px;background:rgba(34,197,94,.15);
color:var(--pos-success);font-size:13px;font-weight:600;">
<i data-lucide="percent" style="width:14px;height:14px;vertical-align:middle;margin-right:2px;"></i>
Giảm 10%
</div>
}
</div>
@* ═══ PRODUCT DETAILS / CHI TIẾT SẢN PHẨM ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Thông tin sản phẩm</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
@foreach (var detail in _productDetails)
{
<div style="padding:10px 14px;background:var(--pos-bg-interactive);border-radius:8px;">
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">@detail.Label</div>
<div style="font-size:14px;font-weight:600;color:@detail.Color;">@detail.Value</div>
</div>
}
</div>
</div>
@* ═══ ACTIVE PROMOTIONS / KHUYẾN MÃI ĐANG ÁP DỤNG ═══ *@
@if (_hasPromotion)
{
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Khuyến mãi đang áp dụng</div>
@foreach (var promo in _promotions)
{
<div style="display:flex;align-items:center;gap:10px;padding:12px;
background:rgba(34,197,94,.08);border-radius:var(--pos-radius);border:1px solid rgba(34,197,94,.2);margin-bottom:8px;">
<i data-lucide="gift" style="width:18px;height:18px;color:var(--pos-success);flex-shrink:0;"></i>
<div style="flex:1;">
<div style="font-size:13px;font-weight:600;">@promo.Name</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-top:2px;">@promo.Period</div>
</div>
<span style="font-size:14px;font-weight:700;color:var(--pos-success);">-@promo.Discount</span>
</div>
}
</div>
}
@* ═══ PRICE HISTORY / LỊCH SỬ GIÁ ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Lịch sử giá (3 lần thay đổi gần nhất)</div>
@foreach (var history in _priceHistory)
{
<div style="display:flex;align-items:center;padding:10px 0;border-bottom:1px solid var(--pos-border-subtle);font-size:13px;">
<i data-lucide="@history.Icon" style="width:14px;height:14px;color:@history.Color;margin-right:10px;"></i>
<div style="flex:1;">
<span style="font-weight:500;">@FormatPrice(history.OldPrice)</span>
<span style="color:var(--pos-text-tertiary);margin:0 6px;">→</span>
<span style="font-weight:600;color:@history.Color;">@FormatPrice(history.NewPrice)</span>
</div>
<span style="color:var(--pos-text-tertiary);font-size:12px;">@history.Date</span>
</div>
}
</div>
}
</div>
@* ═══ FOOTER / CUỐI TRANG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);">
<button style="width:100%;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;font-weight:500;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"></i>
Đóng
</button>
</div>
</div>
</div>
@code {
// EN: Price check state / VI: Trạng thái kiểm tra giá
private string _searchInput = "APL-001";
private bool _productFound = true;
private string _productName = "Áo polo nam";
private decimal _originalPrice = 450_000;
private decimal _currentPrice = 405_000;
private bool _hasPromotion = true;
// EN: Product details / VI: Chi tiết sản phẩm
private readonly List<DetailItem> _productDetails = new()
{
new("Tên sản phẩm", "Áo polo nam", "var(--pos-text-primary)"),
new("SKU", "APL-001", "var(--pos-text-primary)"),
new("Danh mục", "Thời trang nam", "var(--pos-text-primary)"),
new("Tồn kho", "156 cái", "var(--pos-success)"),
new("Giá gốc", "450,000₫", "var(--pos-text-primary)"),
new("Giá hiện tại", "405,000₫", "var(--pos-orange-primary)"),
};
// EN: Active promotions / VI: Khuyến mãi đang áp dụng
private readonly List<PromoInfo> _promotions = new()
{
new("Giảm giá mùa hè 2026", "01/02/2026 — 31/03/2026", "10%"),
};
// EN: Price history / VI: Lịch sử giá
private readonly List<PriceHistoryItem> _priceHistory = new()
{
new(500_000, 450_000, "01/01/2026", "trending-down", "var(--pos-success)"),
new(420_000, 500_000, "15/11/2025", "trending-up", "var(--pos-danger)"),
new(450_000, 420_000, "01/09/2025", "trending-down", "var(--pos-success)"),
};
private void SearchProduct()
{
_productFound = true;
}
private record DetailItem(string Label, string Value, string Color);
private record PromoInfo(string Name, string Period, string Discount);
private record PriceHistoryItem(decimal OldPrice, decimal NewPrice, string Date, string Icon, string Color);
}

View File

@@ -0,0 +1,235 @@
@*
EN: Split Bill — Equal split, by-item split, custom split modes for shared bills.
VI: Tách hóa đơn — Chia đều, chia theo món, chia tùy chỉnh cho hóa đơn chung.
*@
@page "/pos/dialog/split-bill"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:720px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:92vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(59,130,246,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="split" style="width:20px;height:20px;color:#3B82F6;"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Tách hóa đơn</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Tổng: @FormatPrice(_billTotal) · Bàn 3 · 6 khách</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
@* ═══ SPLIT MODE TABS / TAB CHẾ ĐỘ CHIA ═══ *@
<div style="padding:12px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;gap:8px;">
@foreach (var mode in _modes)
{
<button style="flex:1;padding:10px;border-radius:var(--pos-radius);border:2px solid @(_activeMode == mode.Key ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_activeMode == mode.Key ? "rgba(255,92,0,.1)" : "transparent");color:var(--pos-text-primary);
cursor:pointer;font-size:13px;font-weight:@(_activeMode == mode.Key ? "600" : "400");"
@onclick="() => _activeMode = mode.Key">
@mode.Label
</button>
}
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@if (_activeMode == "equal")
{
@* ═══ EQUAL SPLIT / CHIA ĐỀU ═══ *@
<div style="text-align:center;margin-bottom:24px;">
<div style="font-size:14px;font-weight:600;margin-bottom:16px;">Số người chia</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:20px;">
@for (var i = 2; i <= 10; i++)
{
var count = i;
<button style="width:44px;height:44px;border-radius:10px;border:2px solid @(_splitCount == count ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_splitCount == count ? "rgba(255,92,0,.1)" : "var(--pos-bg-interactive)");
color:var(--pos-text-primary);cursor:pointer;font-size:16px;font-weight:600;"
@onclick="() => _splitCount = count">
@count
</button>
}
</div>
<div style="font-size:42px;font-weight:700;color:var(--pos-orange-primary);margin-bottom:8px;">
@FormatPrice(Math.Round(_billTotal / _splitCount))
</div>
<div style="font-size:14px;color:var(--pos-text-tertiary);">mỗi người</div>
</div>
@* EN: Per-person breakdown / VI: Chi tiết từng người *@
<div style="display:grid;grid-template-columns:repeat(3, 1fr);gap:10px;">
@for (var i = 1; i <= _splitCount; i++)
{
var personNum = i;
<div style="background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:14px;text-align:center;">
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-bottom:6px;">Người @personNum</div>
<div style="font-size:16px;font-weight:700;color:var(--pos-orange-primary);">@FormatPrice(Math.Round(_billTotal / _splitCount))</div>
</div>
}
</div>
}
else if (_activeMode == "byitem")
{
@* ═══ BY-ITEM SPLIT / CHIA THEO MÓN ═══ *@
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
@for (var p = 0; p < 3; p++)
{
var personIdx = p;
<div style="background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:14px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;text-align:center;
padding-bottom:8px;border-bottom:1px solid var(--pos-border-subtle);">
Người @(personIdx + 1)
</div>
@foreach (var item in _billItems.Where(i => i.AssignedTo == personIdx))
{
<div style="display:flex;justify-content:space-between;padding:6px 0;font-size:12px;">
<span>@item.Name</span>
<span style="font-weight:600;">@FormatPrice(item.Price)</span>
</div>
}
<div style="display:flex;justify-content:space-between;padding-top:8px;margin-top:8px;
border-top:1px solid var(--pos-border-subtle);font-size:14px;font-weight:700;">
<span>Tổng</span>
<span style="color:var(--pos-orange-primary);">@FormatPrice(_billItems.Where(i => i.AssignedTo == personIdx).Sum(i => i.Price))</span>
</div>
</div>
}
</div>
@* EN: Unassigned items / VI: Món chưa gán *@
@if (UnassignedItems.Any())
{
<div style="margin-top:16px;padding:14px;background:rgba(245,158,11,.08);border-radius:var(--pos-radius);
border:1px dashed var(--pos-warning);">
<div style="font-size:13px;font-weight:600;color:var(--pos-warning);margin-bottom:8px;">Chưa phân</div>
@foreach (var item in UnassignedItems)
{
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;font-size:12px;">
<span>@item.Name — @FormatPrice(item.Price)</span>
<div style="display:flex;gap:4px;">
@for (var p = 0; p < 3; p++)
{
var targetPerson = p;
<button style="width:28px;height:28px;border-radius:6px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-elevated);color:var(--pos-text-primary);cursor:pointer;font-size:11px;"
@onclick="() => item.AssignedTo = targetPerson">
@(targetPerson + 1)
</button>
}
</div>
</div>
}
</div>
}
}
else
{
@* ═══ CUSTOM SPLIT / CHIA TÙY CHỈNH ═══ *@
<div style="display:flex;flex-direction:column;gap:12px;">
@for (var i = 0; i < 3; i++)
{
var idx = i;
<div style="display:flex;align-items:center;gap:12px;background:var(--pos-bg-interactive);
border-radius:var(--pos-radius);padding:14px;">
<div style="width:36px;height:36px;border-radius:8px;background:var(--pos-bg-elevated);
display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;">
@(idx + 1)
</div>
<div style="flex:1;">
<div style="font-size:13px;font-weight:600;margin-bottom:4px;">Người @(idx + 1)</div>
<input type="number" @bind="_customAmounts[idx]" placeholder="Nhập số tiền..."
style="width:100%;padding:8px 12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-elevated);color:var(--pos-text-primary);font-size:14px;font-weight:600;" />
</div>
<div style="font-size:14px;font-weight:700;color:var(--pos-orange-primary);min-width:80px;text-align:right;">
@FormatPrice(_customAmounts[idx])
</div>
</div>
}
</div>
@* EN: Remaining amount / VI: Số tiền còn lại *@
<div style="margin-top:16px;padding:14px;border-radius:var(--pos-radius);
background:@(CustomRemaining == 0 ? "rgba(34,197,94,.1)" : "rgba(245,158,11,.1)");
border:1px solid @(CustomRemaining == 0 ? "rgba(34,197,94,.3)" : "rgba(245,158,11,.3)");
display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:13px;font-weight:600;color:@(CustomRemaining == 0 ? "var(--pos-success)" : "var(--pos-warning)");">
@(CustomRemaining == 0 ? "Đã chia hết!" : "Còn thiếu")
</span>
<span style="font-size:16px;font-weight:700;color:@(CustomRemaining == 0 ? "var(--pos-success)" : "var(--pos-warning)");">
@FormatPrice(Math.Abs(CustomRemaining))
</span>
</div>
}
</div>
@* ═══ FOOTER / CUỐI TRANG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;"
@onclick="@(() => NavigateTo(""))">
Hủy
</button>
<button style="flex:2;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-orange-primary);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="GenerateSplitBills">
<i data-lucide="receipt" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Tạo hóa đơn riêng
</button>
</div>
</div>
</div>
@code {
// EN: Split state / VI: Trạng thái tách
private string _activeMode = "equal";
private int _splitCount = 3;
private decimal _billTotal = 850_000;
// EN: Split mode definitions / VI: Định nghĩa chế độ chia
private readonly List<SplitMode> _modes = new()
{
new("equal", "Chia đều"),
new("byitem", "Chia theo món"),
new("custom", "Chia tùy chỉnh"),
};
// EN: Bill items for by-item split / VI: Danh sách món để chia theo món
private readonly List<SplitItem> _billItems = new()
{
new("Lẩu thái", 250_000, 0),
new("Phở bò tái", 75_000, 0),
new("Cơm tấm sườn", 65_000, 1),
new("Cá kho tộ", 120_000, 1),
new("Gỏi cuốn", 45_000, 2),
new("Chả giò", 40_000, 2),
new("Bia Sài Gòn", 75_000, -1),
new("Trà đá", 40_000, -1),
new("Bánh flan", 50_000, -1),
new("Nước mía", 90_000, -1),
};
// EN: Custom amounts / VI: Số tiền tùy chỉnh
private decimal[] _customAmounts = { 300_000, 300_000, 250_000 };
// EN: Computed properties for template / VI: Thuộc tính tính toán cho template
private List<SplitItem> UnassignedItems => _billItems.Where(i => i.AssignedTo < 0).ToList();
private decimal CustomRemaining => _billTotal - _customAmounts.Sum();
private void GenerateSplitBills() => NavigateTo("");
private record SplitMode(string Key, string Label);
private class SplitItem(string name, decimal price, int assignedTo)
{
public string Name { get; set; } = name;
public decimal Price { get; set; } = price;
public int AssignedTo { get; set; } = assignedTo;
}
}

View File

@@ -0,0 +1,188 @@
@*
EN: Quick Stock-In Dialog — Product search, qty, supplier, unit cost, lot, expiry, notes, recent log.
VI: Nhập kho nhanh — Tìm sản phẩm, SL, nhà cung cấp, giá nhập, lô, hạn dùng, ghi chú, lịch sử gần đây.
*@
@page "/pos/dialog/stock-in"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:600px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:92vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(34,197,94,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="package-plus" style="width:20px;height:20px;color:var(--pos-success);"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Nhập kho</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Nhập hàng hóa vào kho</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ PRODUCT SEARCH / TÌM SẢN PHẨM ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Sản phẩm</div>
<div style="display:flex;gap:8px;">
<input type="text" @bind="_productSearch" placeholder="Tìm sản phẩm hoặc mã SKU..."
style="flex:1;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;" />
<button style="padding:12px;border-radius:var(--pos-radius);background:var(--pos-bg-interactive);
border:1px solid var(--pos-border-default);color:var(--pos-text-primary);cursor:pointer;">
<i data-lucide="scan-barcode" style="width:18px;height:18px;"></i>
</button>
</div>
@if (!string.IsNullOrEmpty(_selectedProduct))
{
<div style="margin-top:10px;padding:12px;background:rgba(34,197,94,.08);border-radius:var(--pos-radius);
border:1px solid rgba(34,197,94,.2);display:flex;align-items:center;gap:10px;">
<i data-lucide="check-circle" style="width:18px;height:18px;color:var(--pos-success);"></i>
<div>
<div style="font-size:14px;font-weight:600;">@_selectedProduct</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">SKU: @_selectedSku · Tồn kho: @_currentStock</div>
</div>
</div>
}
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;">
@* ═══ QUANTITY / SỐ LƯỢNG ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Số lượng nhập</div>
<input type="number" @bind="_quantity" min="1"
style="width:100%;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:16px;font-weight:600;box-sizing:border-box;" />
</div>
@* ═══ UNIT COST / GIÁ NHẬP ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Giá nhập (₫/đơn vị)</div>
<input type="number" @bind="_unitCost"
style="width:100%;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:16px;font-weight:600;box-sizing:border-box;" />
</div>
</div>
@* ═══ SUPPLIER / NHÀ CUNG CẤP ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Nhà cung cấp</div>
<select @bind="_supplier"
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;appearance:auto;">
<option value="">— Chọn nhà cung cấp —</option>
@foreach (var s in _suppliers)
{
<option value="@s">@s</option>
}
</select>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;">
@* ═══ LOT / LÔ HÀNG ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Số lô / Batch</div>
<input type="text" @bind="_lotNumber" placeholder="VD: LOT-2026-0215"
style="width:100%;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;box-sizing:border-box;" />
</div>
@* ═══ EXPIRY / HẠN DÙNG ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Hạn sử dụng</div>
<input type="date" @bind="_expiryDate"
style="width:100%;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;box-sizing:border-box;" />
</div>
</div>
@* ═══ NOTES / GHI CHÚ ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Ghi chú</div>
<textarea @bind="_notes" rows="2" placeholder="Ghi chú thêm..."
style="width:100%;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;resize:vertical;box-sizing:border-box;"></textarea>
</div>
@* ═══ TOTAL COST / TỔNG CHI PHÍ ═══ *@
<div style="display:flex;justify-content:space-between;padding:14px;background:var(--pos-bg-interactive);
border-radius:var(--pos-radius);margin-bottom:20px;">
<span style="font-size:14px;font-weight:600;">Tổng chi phí nhập</span>
<span style="font-size:18px;font-weight:700;color:var(--pos-orange-primary);">@FormatPrice(_quantity * _unitCost)</span>
</div>
@* ═══ RECENT LOG / NHẬT KÝ GẦN ĐÂY ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Nhập kho gần đây</div>
@foreach (var log in _recentLogs)
{
<div style="display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--pos-border-subtle);font-size:12px;">
<i data-lucide="package" style="width:14px;height:14px;color:var(--pos-text-tertiary);"></i>
<div style="flex:1;">
<span style="font-weight:500;">@log.Product</span>
<span style="color:var(--pos-text-tertiary);"> · @log.Qty @log.Unit</span>
</div>
<span style="color:var(--pos-text-tertiary);">@log.Time</span>
</div>
}
</div>
</div>
@* ═══ FOOTER / CUỐI TRANG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;"
@onclick="@(() => NavigateTo(""))">
Hủy
</button>
<button style="flex:2;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-success);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="ConfirmStockIn">
<i data-lucide="check" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Xác nhận nhập kho
</button>
</div>
</div>
</div>
@code {
// EN: Stock-in state / VI: Trạng thái nhập kho
private string _productSearch = "Cà phê hạt Arabica";
private string _selectedProduct = "Cà phê hạt Arabica";
private string _selectedSku = "CF-ARA-500";
private int _currentStock = 120;
private int _quantity = 50;
private decimal _unitCost = 185_000;
private string _supplier = "Công ty TNHH Cà phê Đà Lạt";
private string _lotNumber = "LOT-2026-0226";
private DateTime _expiryDate = new(2027, 2, 26);
private string _notes = "";
// EN: Suppliers / VI: Nhà cung cấp
private readonly string[] _suppliers =
{
"Công ty TNHH Cà phê Đà Lạt",
"Nhà phân phối Sài Gòn Food",
"Đại lý nông sản Tây Nguyên",
"Công ty CP Thực phẩm Miền Nam",
};
// EN: Recent stock-in log / VI: Nhật ký nhập gần đây
private readonly List<StockLog> _recentLogs = new()
{
new("Cà phê hạt Robusta", 30, "kg", "Hôm nay 09:15"),
new("Sữa tươi TH", 100, "hộp", "Hôm nay 08:30"),
new("Đường trắng", 20, "kg", "Hôm qua 16:00"),
new("Trà ô long", 15, "kg", "Hôm qua 14:20"),
new("Ly giấy 16oz", 500, "cái", "25/02/2026"),
};
private void ConfirmStockIn() => NavigateTo("");
private record StockLog(string Product, int Qty, string Unit, string Time);
}

View File

@@ -0,0 +1,158 @@
@*
EN: Stock-Out Dialog — Product search, current stock, remove qty, reason, authorization, notes.
VI: Xuất kho — Tìm sản phẩm, tồn kho hiện tại, SL xuất, lý do, xác nhận nhân viên, ghi chú.
*@
@page "/pos/dialog/stock-out"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:540px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:90vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(239,68,68,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="package-minus" style="width:20px;height:20px;color:var(--pos-danger);"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Xuất kho</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Xuất hàng hóa khỏi kho</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ PRODUCT SEARCH / TÌM SẢN PHẨM ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Sản phẩm</div>
<input type="text" @bind="_productSearch" placeholder="Tìm sản phẩm hoặc mã SKU..."
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;box-sizing:border-box;" />
</div>
@* ═══ SELECTED PRODUCT / SẢN PHẨM ĐÃ CHỌN ═══ *@
<div style="background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:16px;margin-bottom:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div>
<div style="font-size:15px;font-weight:700;">@_selectedProduct</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">SKU: @_selectedSku</div>
</div>
</div>
@* EN: Current stock level / VI: Mức tồn kho hiện tại *@
<div style="display:flex;gap:12px;">
<div style="flex:1;background:var(--pos-bg-elevated);border-radius:8px;padding:10px;text-align:center;">
<div style="font-size:11px;color:var(--pos-text-tertiary);">Tồn kho</div>
<div style="font-size:20px;font-weight:700;color:@(_currentStock <= 10 ? "var(--pos-danger)" : "var(--pos-success)");">@_currentStock</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);">@_unit</div>
</div>
<div style="flex:1;background:var(--pos-bg-elevated);border-radius:8px;padding:10px;text-align:center;">
<div style="font-size:11px;color:var(--pos-text-tertiary);">Sau xuất</div>
<div style="font-size:20px;font-weight:700;color:@((_currentStock - _removeQty) <= 5 ? "var(--pos-danger)" : "var(--pos-text-primary)");">@(_currentStock - _removeQty)</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);">@_unit</div>
</div>
</div>
</div>
@* ═══ QUANTITY TO REMOVE / SỐ LƯỢNG XUẤT ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Số lượng xuất</div>
<div style="display:flex;align-items:center;gap:10px;">
<button style="width:40px;height:40px;border-radius:8px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);cursor:pointer;font-size:18px;"
@onclick="() => _removeQty = Math.Max(1, _removeQty - 1)"></button>
<input type="number" @bind="_removeQty" min="1" max="@_currentStock"
style="width:100px;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:18px;font-weight:700;text-align:center;" />
<button style="width:40px;height:40px;border-radius:8px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);cursor:pointer;font-size:18px;"
@onclick="() => _removeQty = Math.Min(_currentStock, _removeQty + 1)">+</button>
<span style="font-size:13px;color:var(--pos-text-tertiary);">@_unit</span>
</div>
</div>
@* ═══ REASON / LÝ DO ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Lý do xuất kho</div>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
@foreach (var reason in _reasons)
{
<button style="padding:10px 16px;border-radius:var(--pos-radius);border:2px solid @(_selectedReason == reason ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_selectedReason == reason ? "rgba(255,92,0,.1)" : "var(--pos-bg-interactive)");
color:var(--pos-text-primary);cursor:pointer;font-size:13px;"
@onclick="() => _selectedReason = reason">
@reason
</button>
}
</div>
</div>
@* ═══ AUTHORIZED BY / NHÂN VIÊN XÁC NHẬN ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Nhân viên xác nhận</div>
<select @bind="_authorizedBy"
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;appearance:auto;">
<option value="">— Chọn nhân viên —</option>
@foreach (var staff in _staffList)
{
<option value="@staff">@staff</option>
}
</select>
</div>
@* ═══ NOTES / GHI CHÚ ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Ghi chú</div>
<textarea @bind="_notes" rows="2" placeholder="Chi tiết thêm..."
style="width:100%;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;resize:vertical;box-sizing:border-box;"></textarea>
</div>
</div>
@* ═══ FOOTER / CUỐI TRANG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;"
@onclick="@(() => NavigateTo(""))">
Hủy
</button>
<button style="flex:2;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-danger);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="ConfirmStockOut">
<i data-lucide="package-minus" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Xác nhận xuất kho
</button>
</div>
</div>
</div>
@code {
// EN: Stock-out state / VI: Trạng thái xuất kho
private string _productSearch = "Sữa tươi";
private string _selectedProduct = "Sữa tươi TH True Milk";
private string _selectedSku = "STM-1L";
private int _currentStock = 48;
private string _unit = "hộp";
private int _removeQty = 5;
private string _selectedReason = "Hư hỏng";
private string _authorizedBy = "Trần Văn C";
private string _notes = "5 hộp bị phồng, hạn còn 2 ngày";
// EN: Reasons / VI: Lý do
private readonly string[] _reasons =
{
"Hư hỏng", "Hết hạn", "Chuyển kho", "Tiêu thụ nội bộ", "Khác"
};
// EN: Staff list / VI: Danh sách nhân viên
private readonly string[] _staffList =
{
"Nguyễn Văn A", "Trần Văn C", "Lê Thị D", "Phạm Minh E"
};
private void ConfirmStockOut() => NavigateTo("");
}

View File

@@ -0,0 +1,197 @@
@*
EN: Stock Transfer — Transfer products between branches with product list, qty, delivery note.
VI: Chuyển kho — Chuyển sản phẩm giữa chi nhánh với danh sách, SL, phiếu giao hàng.
*@
@page "/pos/dialog/stock-transfer"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:680px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:92vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(59,130,246,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="arrow-left-right" style="width:20px;height:20px;color:#3B82F6;"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Chuyển kho</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Chuyển hàng giữa các chi nhánh</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@* ═══ BRANCH SELECTION / CHỌN CHI NHÁNH ═══ *@
<div style="display:flex;gap:12px;align-items:center;margin-bottom:24px;">
<div style="flex:1;">
<div style="font-size:14px;font-weight:600;margin-bottom:8px;">Từ chi nhánh</div>
<select @bind="_fromBranch"
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;appearance:auto;">
@foreach (var b in _branches)
{
<option value="@b">@b</option>
}
</select>
</div>
<div style="padding-top:24px;">
<i data-lucide="arrow-right" style="width:24px;height:24px;color:var(--pos-orange-primary);"></i>
</div>
<div style="flex:1;">
<div style="font-size:14px;font-weight:600;margin-bottom:8px;">Đến chi nhánh</div>
<select @bind="_toBranch"
style="width:100%;padding:12px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;appearance:auto;">
@foreach (var b in _branches)
{
<option value="@b">@b</option>
}
</select>
</div>
</div>
@* ═══ ADD PRODUCT ROW / THÊM SẢN PHẨM ═══ *@
<div style="margin-bottom:16px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Thêm sản phẩm</div>
<div style="display:flex;gap:8px;">
<input type="text" @bind="_addProductSearch" placeholder="Tìm sản phẩm..."
style="flex:1;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;" />
<input type="number" @bind="_addQty" min="1" placeholder="SL"
style="width:80px;padding:10px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;text-align:center;" />
<button style="padding:10px 16px;border-radius:var(--pos-radius);background:var(--pos-orange-primary);
border:none;color:#fff;cursor:pointer;font-size:13px;font-weight:600;"
@onclick="AddTransferItem">
<i data-lucide="plus" style="width:14px;height:14px;vertical-align:middle;"></i> Thêm
</button>
</div>
</div>
@* ═══ TRANSFER LIST / DANH SÁCH CHUYỂN ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Danh sách sản phẩm chuyển</div>
<div style="border:1px solid var(--pos-border-default);border-radius:var(--pos-radius);overflow:hidden;">
@* EN: Table header / VI: Tiêu đề bảng *@
<div style="display:flex;padding:10px 14px;background:var(--pos-bg-interactive);font-size:12px;font-weight:600;color:var(--pos-text-tertiary);">
<span style="flex:2;">Sản phẩm</span>
<span style="width:60px;text-align:center;">Tồn kho</span>
<span style="width:80px;text-align:center;">SL chuyển</span>
<span style="width:90px;text-align:right;">Giá trị</span>
<span style="width:36px;"></span>
</div>
@foreach (var item in _transferItems)
{
<div style="display:flex;align-items:center;padding:12px 14px;border-top:1px solid var(--pos-border-subtle);font-size:13px;">
<div style="flex:2;">
<div style="font-weight:500;">@item.Name</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);">@item.Sku</div>
</div>
<span style="width:60px;text-align:center;color:var(--pos-text-tertiary);">@item.Stock</span>
<div style="width:80px;display:flex;justify-content:center;">
<input type="number" @bind="item.Qty" min="1" max="@item.Stock"
style="width:56px;padding:6px;border-radius:6px;border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;text-align:center;" />
</div>
<span style="width:90px;text-align:right;font-weight:600;">@FormatPrice(item.UnitPrice * item.Qty)</span>
<button style="width:36px;background:none;border:none;color:var(--pos-danger);cursor:pointer;text-align:center;"
@onclick="() => _transferItems.Remove(item)">
<i data-lucide="trash-2" style="width:14px;height:14px;"></i>
</button>
</div>
}
</div>
</div>
@* ═══ TRANSFER SUMMARY / TÓM TẮT CHUYỂN ═══ *@
<div style="display:flex;justify-content:space-between;padding:14px;background:var(--pos-bg-interactive);
border-radius:var(--pos-radius);margin-bottom:20px;">
<div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Tổng sản phẩm</div>
<div style="font-size:16px;font-weight:700;">@_transferItems.Count mặt hàng</div>
</div>
<div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Tổng số lượng</div>
<div style="font-size:16px;font-weight:700;">@_transferItems.Sum(i => i.Qty)</div>
</div>
<div style="text-align:right;">
<div style="font-size:12px;color:var(--pos-text-tertiary);">Tổng giá trị</div>
<div style="font-size:16px;font-weight:700;color:var(--pos-orange-primary);">@FormatPrice(_transferItems.Sum(i => i.UnitPrice * i.Qty))</div>
</div>
</div>
@* ═══ DELIVERY NOTE / GHI CHÚ GIAO HÀNG ═══ *@
<div>
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Ghi chú giao hàng</div>
<textarea @bind="_deliveryNote" rows="2" placeholder="Ghi chú cho phiếu chuyển kho..."
style="width:100%;padding:10px 14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:13px;resize:vertical;box-sizing:border-box;"></textarea>
</div>
</div>
@* ═══ FOOTER / CUỐI TRANG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;"
@onclick="@(() => NavigateTo(""))">
Hủy
</button>
<button style="flex:2;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-orange-primary);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="CreateTransfer">
<i data-lucide="send" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
Tạo phiếu chuyển kho
</button>
</div>
</div>
</div>
@code {
// EN: Transfer state / VI: Trạng thái chuyển kho
private string _fromBranch = "Chi nhánh Q1";
private string _toBranch = "Chi nhánh Q7";
private string _addProductSearch = "";
private int _addQty = 10;
private string _deliveryNote = "";
// EN: Branches / VI: Chi nhánh
private readonly string[] _branches =
{
"Chi nhánh Q1", "Chi nhánh Q3", "Chi nhánh Q7", "Chi nhánh Thủ Đức", "Chi nhánh Bình Thạnh"
};
// EN: Demo transfer items / VI: Danh sách mẫu
private readonly List<TransferItem> _transferItems = new()
{
new("Cà phê hạt Arabica", "CF-ARA-500", 120, 185_000, 20),
new("Trà ô long", "TO-OL-250", 85, 95_000, 15),
new("Sữa tươi TH 1L", "STM-1L", 200, 32_000, 50),
new("Ly giấy 16oz", "LG-16", 1200, 2_500, 300),
};
private void AddTransferItem()
{
if (!string.IsNullOrWhiteSpace(_addProductSearch))
{
_transferItems.Add(new TransferItem(_addProductSearch, "NEW-SKU", 100, 50_000, _addQty));
_addProductSearch = "";
_addQty = 10;
}
}
private void CreateTransfer() => NavigateTo("");
private class TransferItem(string name, string sku, int stock, decimal unitPrice, int qty)
{
public string Name { get; set; } = name;
public string Sku { get; set; } = sku;
public int Stock { get; set; } = stock;
public decimal UnitPrice { get; set; } = unitPrice;
public int Qty { get; set; } = qty;
}
}

View File

@@ -0,0 +1,198 @@
@*
EN: Void / Refund Dialog — Order lookup, void vs refund selection, reason, manager PIN, confirm.
VI: Hủy / Hoàn tiền — Tra cứu đơn hàng, chọn hủy hay hoàn tiền, lý do, mã PIN quản lý, xác nhận.
*@
@page "/pos/dialog/void-refund"
@layout PosLayout
@inherits PosBase
<div class="pos-dialog-overlay">
<div style="width:100%;max-width:560px;background:var(--pos-bg-elevated);border-radius:16px;
display:flex;flex-direction:column;max-height:90vh;overflow:hidden;">
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
<div style="padding:20px 24px;border-bottom:1px solid var(--pos-border-subtle);display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background:rgba(239,68,68,.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="rotate-ccw" style="width:20px;height:20px;color:var(--pos-danger);"></i>
</div>
<div style="flex:1;">
<div style="font-size:18px;font-weight:700;">Hủy / Hoàn tiền</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Void / Refund đơn hàng</div>
</div>
<button style="background:none;border:none;color:var(--pos-text-tertiary);cursor:pointer;padding:8px;"
@onclick="@(() => NavigateTo(""))">
<i data-lucide="x" style="width:20px;height:20px;"></i>
</button>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;">
@if (!_orderFound)
{
@* ═══ ORDER LOOKUP / TRA CỨU ĐƠN HÀNG ═══ *@
<div style="text-align:center;padding:24px 0;">
<div style="font-size:14px;font-weight:600;margin-bottom:16px;">Nhập mã đơn hàng</div>
<div style="display:flex;gap:10px;max-width:360px;margin:0 auto;">
<input type="text" @bind="_orderNumber" placeholder="VD: DH2024-0567"
style="flex:1;padding:12px 16px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:14px;" />
<button style="padding:12px 20px;border-radius:var(--pos-radius);background:var(--pos-orange-primary);
border:none;color:#fff;font-weight:600;cursor:pointer;font-size:14px;"
@onclick="LookupOrder">
<i data-lucide="search" style="width:16px;height:16px;vertical-align:middle;"></i> Tra cứu
</button>
</div>
</div>
}
else
{
@* ═══ ORDER DETAILS / CHI TIẾT ĐƠN HÀNG ═══ *@
<div style="background:var(--pos-bg-interactive);border-radius:var(--pos-radius);padding:16px;margin-bottom:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div>
<span style="font-size:15px;font-weight:700;">Đơn #@_orderNumber</span>
<span style="font-size:12px;color:var(--pos-text-tertiary);margin-left:8px;">14:32 — 26/02/2026</span>
</div>
<span style="font-size:12px;padding:4px 10px;border-radius:6px;background:rgba(34,197,94,.15);color:var(--pos-success);font-weight:600;">
Đã thanh toán
</span>
</div>
@foreach (var item in _orderItems)
{
<div style="display:flex;justify-content:space-between;padding:6px 0;font-size:13px;border-bottom:1px solid var(--pos-border-subtle);">
<span>x@(item.Qty) @item.Name</span>
<span style="font-weight:600;">@FormatPrice(item.Price * item.Qty)</span>
</div>
}
<div style="display:flex;justify-content:space-between;padding-top:10px;font-size:15px;font-weight:700;">
<span>Tổng cộng</span>
<span style="color:var(--pos-orange-primary);">@FormatPrice(_orderTotal)</span>
</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-top:6px;">
Thanh toán: Tiền mặt · PV: Nguyễn Văn A
</div>
</div>
@* ═══ TYPE SELECTION / CHỌN LOẠI ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Loại xử lý</div>
<div style="display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:2px solid @(_type == "void" ? "var(--pos-danger)" : "var(--pos-border-default)");
background:@(_type == "void" ? "rgba(239,68,68,.1)" : "var(--pos-bg-interactive)");color:var(--pos-text-primary);cursor:pointer;text-align:center;"
@onclick='() => _type = "void"'>
<i data-lucide="x-circle" style="width:20px;height:20px;display:block;margin:0 auto 6px;color:var(--pos-danger);"></i>
<div style="font-size:14px;font-weight:600;">Hủy đơn (Void)</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-top:4px;">Xóa hoàn toàn đơn hàng</div>
</button>
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:2px solid @(_type == "refund" ? "var(--pos-warning)" : "var(--pos-border-default)");
background:@(_type == "refund" ? "rgba(245,158,11,.1)" : "var(--pos-bg-interactive)");color:var(--pos-text-primary);cursor:pointer;text-align:center;"
@onclick='() => _type = "refund"'>
<i data-lucide="undo-2" style="width:20px;height:20px;display:block;margin:0 auto 6px;color:var(--pos-warning);"></i>
<div style="font-size:14px;font-weight:600;">Hoàn tiền (Refund)</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-top:4px;">Trả lại tiền cho khách</div>
</button>
</div>
</div>
@* ═══ REASON SELECTION / CHỌN LÝ DO ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Lý do</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
@foreach (var reason in _reasons)
{
<button style="padding:10px 14px;border-radius:var(--pos-radius);border:2px solid @(_selectedReason == reason ? "var(--pos-orange-primary)" : "var(--pos-border-default)");
background:@(_selectedReason == reason ? "rgba(255,92,0,.1)" : "var(--pos-bg-interactive)");color:var(--pos-text-primary);cursor:pointer;font-size:13px;text-align:left;"
@onclick="() => _selectedReason = reason">
@reason
</button>
}
</div>
</div>
@if (_type == "refund")
{
@* ═══ REFUND AMOUNT / SỐ TIỀN HOÀN TRẢ ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Số tiền hoàn trả</div>
<div style="display:flex;gap:10px;align-items:center;">
<input type="number" @bind="_refundAmount"
style="flex:1;padding:12px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);font-size:16px;font-weight:600;" />
<span style="font-size:14px;color:var(--pos-text-tertiary);">/ @FormatPrice(_orderTotal)</span>
</div>
</div>
}
@* ═══ MANAGER PIN / MÃ PIN QUẢN LÝ ═══ *@
<div style="margin-bottom:20px;">
<div style="font-size:14px;font-weight:600;margin-bottom:10px;">Mã PIN quản lý</div>
<div style="display:flex;gap:8px;">
@for (var i = 0; i < 4; i++)
{
var idx = i;
<input type="password" maxlength="1"
style="width:48px;height:48px;text-align:center;font-size:20px;font-weight:700;
border-radius:var(--pos-radius);border:2px solid var(--pos-border-default);
background:var(--pos-bg-interactive);color:var(--pos-text-primary);" />
}
</div>
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-top:6px;">Yêu cầu xác nhận từ quản lý</div>
</div>
}
</div>
@if (_orderFound)
{
@* ═══ ACTIONS / HÀNH ĐỘNG ═══ *@
<div style="padding:16px 24px;border-top:1px solid var(--pos-border-subtle);display:flex;gap:10px;">
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:14px;font-weight:500;"
@onclick="@(() => NavigateTo(""))">
Hủy bỏ
</button>
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);border:none;
background:var(--pos-danger);color:#fff;cursor:pointer;font-size:14px;font-weight:600;"
@onclick="ConfirmAction">
<i data-lucide="alert-triangle" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
@(_type == "void" ? "Xác nhận hủy đơn" : $"Hoàn trả {FormatPrice(_refundAmount)}")
</button>
</div>
}
</div>
</div>
@code {
// EN: Order lookup state / VI: Trạng thái tra cứu đơn
private string _orderNumber = "DH2024-0567";
private bool _orderFound = false;
private string _type = "void";
private string _selectedReason = "";
private decimal _refundAmount = 285_000;
private decimal _orderTotal = 285_000;
// EN: Reason options / VI: Các lý do
private readonly string[] _reasons =
{
"Khách yêu cầu", "Sai đơn hàng", "Vấn đề chất lượng", "Khác"
};
// EN: Demo order items / VI: Mục đơn hàng mẫu
private readonly List<OrderItem> _orderItems = new()
{
new("Phở bò tái", 75_000, 2),
new("Gỏi cuốn tôm thịt", 45_000, 1),
new("Trà đá", 10_000, 3),
new("Bánh flan", 25_000, 2),
};
private void LookupOrder()
{
_orderFound = true;
}
private void ConfirmAction()
{
NavigateTo("");
}
private record OrderItem(string Name, decimal Price, int Qty);
}