173 lines
6.6 KiB
Plaintext
173 lines
6.6 KiB
Plaintext
@*
|
|
EN: Restaurant POS Desktop — Table map grid + order panel for dine-in service.
|
|
VI: POS Nhà hàng Desktop — Lưới sơ đồ bàn + panel đặt món cho phục vụ tại chỗ.
|
|
*@
|
|
@page "/pos/{ShopId:guid}/restaurant"
|
|
@layout PosLayout
|
|
@inherits PosBase
|
|
@inject WebClientTpos.Client.Services.PosDataService DataService
|
|
|
|
<div class="pos-product-panel" style="padding:16px;overflow-y:auto;">
|
|
@if (_isLoading)
|
|
{
|
|
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--pos-text-tertiary);">
|
|
Đang tải...
|
|
</div>
|
|
}
|
|
else if (_loadError)
|
|
{
|
|
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--pos-text-tertiary);">
|
|
Không thể tải dữ liệu
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
@* ═══ SECTION TABS / TAB KHU VỰC ═══ *@
|
|
<div class="pos-category-tabs">
|
|
@foreach (var section in _sections)
|
|
{
|
|
<button class="pos-category-tab @(section == _activeSection ? "pos-category-tab--active" : "")"
|
|
@onclick="() => _activeSection = section">
|
|
@(section)
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
@* ═══ TABLE MAP GRID / LƯỚI SƠ ĐỒ BÀN ═══ *@
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;padding:8px 0;">
|
|
@foreach (var table in FilteredTables)
|
|
{
|
|
<div @onclick="() => SelectTable(table)"
|
|
style="background:@GetStatusColor(table.Status);border-radius:var(--pos-radius);
|
|
padding:16px;text-align:center;cursor:pointer;border:2px solid @(SelectedTable?.Id == table.Id ? "var(--pos-orange-primary)" : "transparent");
|
|
transition:all .2s ease;">
|
|
<div style="font-size:20px;font-weight:700;">@table.Name</div>
|
|
<div style="font-size:12px;color:rgba(255,255,255,.7);margin-top:4px;">@table.Seats chỗ</div>
|
|
<div style="font-size:11px;margin-top:6px;font-weight:600;text-transform:uppercase;">
|
|
@GetStatusLabel(table.Status)
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@* ═══ ORDER PANEL (RIGHT) / PANEL ĐẶT MÓN (PHẢI) ═══ *@
|
|
<div class="pos-cart-panel">
|
|
@if (SelectedTable is not null)
|
|
{
|
|
<div class="pos-cart-header">
|
|
<span class="pos-cart-header__title">@SelectedTable.Name</span>
|
|
<span style="font-size:12px;color:var(--pos-text-tertiary);">@SelectedTable.Seats chỗ</span>
|
|
</div>
|
|
|
|
<div class="pos-cart-items">
|
|
@if (SelectedTable.Status == "occupied")
|
|
{
|
|
@foreach (var item in _demoOrderItems)
|
|
{
|
|
<div class="pos-cart-item">
|
|
<div class="pos-cart-item__info">
|
|
<span class="pos-cart-item__name">@item.Name</span>
|
|
<span class="pos-cart-item__price">@FormatPrice(item.Price)</span>
|
|
</div>
|
|
<span style="font-size:13px;font-weight:600;">x@item.Qty</span>
|
|
</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<div style="text-align:center;padding:32px 16px;color:var(--pos-text-tertiary);font-size:13px;">
|
|
Bàn trống — Nhấn để mở đơn mới
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="pos-cart-footer">
|
|
<div class="pos-cart-total">
|
|
<span class="pos-cart-total__label">Tổng cộng</span>
|
|
<span class="pos-cart-total__value">@FormatPrice(SelectedTable.Status == "occupied" ? _demoOrderItems.Sum(i => i.Price * i.Qty) : 0)</span>
|
|
</div>
|
|
<button class="pos-btn-checkout" @onclick="@(() => NavigateTo("restaurant/table-detail"))">
|
|
<i data-lucide="receipt"></i> Xem hóa đơn
|
|
</button>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div style="flex:1;display:flex;align-items:center;justify-content:center;color:var(--pos-text-tertiary);font-size:14px;">
|
|
Chọn bàn để bắt đầu
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
|
|
// EN: Loading state / VI: Trạng thái tải
|
|
private bool _isLoading = true;
|
|
private bool _loadError;
|
|
|
|
// EN: Active section filter / VI: Bộ lọc khu vực
|
|
private string _activeSection = "Tất cả";
|
|
private string[] _sections = { "Tất cả" };
|
|
|
|
// EN: Selected table reference / VI: Bàn đang chọn
|
|
private TableInfo? SelectedTable { get; set; }
|
|
|
|
// EN: Table data from API / VI: Dữ liệu bàn từ API
|
|
private List<TableInfo> _tables = new();
|
|
|
|
private IEnumerable<TableInfo> FilteredTables =>
|
|
_activeSection == "Tất cả" ? _tables : _tables.Where(t => t.Section == _activeSection);
|
|
|
|
// EN: Demo order items for occupied tables / VI: Món mẫu cho bàn đang phục vụ
|
|
private readonly List<OrderItem> _demoOrderItems = new()
|
|
{
|
|
new("Phở bò tái", 75_000, 2), new("Gỏi cuốn", 45_000, 1),
|
|
new("Cơm tấm sườn", 65_000, 1), new("Trà đá", 10_000, 3),
|
|
};
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
try
|
|
{
|
|
var apiTables = await DataService.GetTablesAsync(ShopId);
|
|
|
|
_tables = apiTables.Select(t => new TableInfo(
|
|
t.Id.ToString(),
|
|
$"Bàn {t.TableNumber}",
|
|
t.Capacity,
|
|
t.Status ?? "available",
|
|
t.Zone ?? "Trong nhà"
|
|
)).ToList();
|
|
|
|
var zones = _tables.Select(t => t.Section).Distinct().ToList();
|
|
_sections = new[] { "Tất cả" }.Concat(zones).ToArray();
|
|
}
|
|
catch
|
|
{
|
|
_loadError = true;
|
|
}
|
|
finally
|
|
{
|
|
_isLoading = false;
|
|
}
|
|
}
|
|
|
|
private void SelectTable(TableInfo table) => SelectedTable = table;
|
|
|
|
private static string GetStatusColor(string status) => status switch
|
|
{
|
|
"available" => "rgba(34,197,94,.15)", "occupied" => "rgba(255,92,0,.18)",
|
|
"reserved" => "rgba(59,130,246,.18)", _ => "var(--pos-bg-interactive)"
|
|
};
|
|
|
|
private static string GetStatusLabel(string status) => status switch
|
|
{
|
|
"available" => "Trống", "occupied" => "Đang phục vụ", "reserved" => "Đã đặt", _ => status
|
|
};
|
|
|
|
private record TableInfo(string Id, string Name, int Seats, string Status, string Section);
|
|
private record OrderItem(string Name, decimal Price, int Qty);
|
|
}
|