Files
pos-system/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Restaurant/RestaurantDesktop.razor

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);
}