- @* EN: Customer lookup / VI: Tìm khách hàng *@
+ @* EN: Customer lookup / VI: Tim khach hang *@
- _phone = e.Value?.ToString() ?? "")" />
-
+ @if (!string.IsNullOrEmpty(_searchError))
+ {
+
@_searchError
+ }
- @* EN: Customer info / VI: Thông tin khách hàng *@
+ @if (_customer != null)
+ {
+ @* EN: Customer info / VI: Thong tin khach hang *@
-
- @_customer.Name[..1]
+
+ @(_customer.DisplayName ?? "?")[..1]
-
@_customer.Name
-
@_customer.Phone
+
@(_customer.DisplayName ?? "Khach hang")
+
@(_customer.Phone ?? _phone)
-
@_customer.Points
-
Điểm
+
@_customer.CurrentExp
+
Diem
-
@_customer.Visits
-
Lần ghé
+
@_customer.TotalExpEarned
+
Tong diem
-
@_customer.Rank
-
Hạng
+
@(_customer.LevelName ?? $"Lv.{_customer.CurrentLevel}")
+
Hang
- @* EN: Stamp grid / VI: Lưới tem *@
+ @* EN: Stamp grid / VI: Luoi tem *@
- Thẻ tích tem
- @_customer.Stamps / @_totalStamps
+ The tich tem
+ @_stampCount / @_totalStamps
@for (int i = 1; i <= _totalStamps; i++)
{
var idx = i;
- var isStamped = idx <= _customer.Stamps;
+ var isStamped = idx <= _stampCount;
var isReward = idx == _totalStamps;
@if (isReward)
{
- 🎁
+ 🎁
}
else if (isStamped)
{
- ☕
+ ☕
}
else
{
@@ -75,54 +94,152 @@
}
- @* EN: Progress bar / VI: Thanh tiến trình *@
+ @* EN: Progress bar / VI: Thanh tien trinh *@
- Còn @(_totalStamps - _customer.Stamps) tem nữa để nhận 1 ly miễn phí!
+ Con @(_totalStamps - _stampCount) tem nua de nhan 1 ly mien phi!
- @* EN: Add stamp button / VI: Nút thêm tem *@
-
-
- Tích tem (+1)
+ @* EN: Add stamp button / VI: Nut them tem *@
+
+ @if (_isAddingStamp)
+ {
+ Dang xu ly...
+ }
+ else
+ {
+
+ Tich tem (+1)
+ }
+ @if (!string.IsNullOrEmpty(_stampMessage))
+ {
+ @_stampMessage
+ }
+ }
+ else if (!_isSearching && _hasSearched)
+ {
+
+ Khong tim thay khach hang. Hay nhap SDT va nhan Tim.
+
+ }
@code {
- // EN: Static UI configuration — does not require DB data / VI: Cấu hình UI tĩnh — không cần dữ liệu từ DB
+ // EN: Search state / VI: Trang thai tim kiem
+ private string _phone = "";
+ private bool _isSearching;
+ private bool _hasSearched;
+ private string? _searchError;
- private string _phone = "0901234567";
+ // EN: Customer data from API / VI: Du lieu khach hang tu API
+ private PosDataService.MemberInfo? _customer;
+ private PosDataService.MemberProgressInfo? _progress;
+
+ // EN: Stamp state / VI: Trang thai tem
private int _totalStamps = 10;
+ private int _stampCount;
+ private bool _isAddingStamp;
+ private string? _stampMessage;
- // EN: Demo customer / VI: Khách hàng mẫu
- private CustomerInfo _customer = new("Nguyễn Minh Anh", "0901234567", 245, 28, "Vàng", 7);
-
- private void AddStamp()
+ private async Task SearchCustomer()
{
- if (_customer.Stamps < _totalStamps)
+ if (string.IsNullOrWhiteSpace(_phone)) return;
+
+ _isSearching = true;
+ _searchError = null;
+ _stampMessage = null;
+ _customer = null;
+ _progress = null;
+ _hasSearched = true;
+
+ try
{
- _customer.Stamps++;
- _customer.Points += 10;
+ // EN: Search using membership API / VI: Tim kiem su dung API thanh vien
+ var members = await DataService.SearchCustomersAsync(ShopId, _phone.Trim());
+
+ if (members.Any())
+ {
+ _customer = members.First();
+
+ // EN: Load member progress for level info / VI: Tai tien trinh thanh vien cho thong tin hang
+ try
+ {
+ _progress = await DataService.GetMemberProgressAsync(_customer.Id);
+ }
+ catch
+ {
+ // EN: Progress is optional / VI: Tien trinh la tuy chon
+ }
+
+ // EN: Calculate stamps from total exp (1 stamp per 10 exp points)
+ // VI: Tinh tem tu tong diem (1 tem moi 10 diem kinh nghiem)
+ _stampCount = (_customer.TotalExpEarned / 10) % _totalStamps;
+ }
}
- else
+ catch
{
- _customer.Stamps = 0; // EN: Reset after reward / VI: Reset sau khi nhận thưởng
+ _searchError = "Khong the tim kiem. Vui long thu lai.";
+ }
+ finally
+ {
+ _isSearching = false;
}
}
- private class CustomerInfo(string name, string phone, int points, int visits, string rank, int stamps)
+ private async Task AddStamp()
{
- public string Name { get; set; } = name;
- public string Phone { get; set; } = phone;
- public int Points { get; set; } = points;
- public int Visits { get; set; } = visits;
- public string Rank { get; set; } = rank;
- public int Stamps { get; set; } = stamps;
+ if (_customer == null) return;
+
+ _isAddingStamp = true;
+ _stampMessage = null;
+
+ try
+ {
+ // EN: Add experience points via API (10 points = 1 stamp)
+ // VI: Them diem kinh nghiem qua API (10 diem = 1 tem)
+ var result = await DataService.AddExperienceAsync(_customer.Id, new PosDataService.AddExpRequest(
+ Points: 10,
+ SourceId: 1, // EN: SourceId 1 = POS purchase / VI: SourceId 1 = Mua hang POS
+ ReferenceId: $"stamp-{DateTime.UtcNow:yyyyMMddHHmmss}"
+ ));
+
+ if (result != null)
+ {
+ _stampCount = (result.TotalExpEarned / 10) % _totalStamps;
+ _customer = _customer with { CurrentExp = result.CurrentExp, TotalExpEarned = result.TotalExpEarned, CurrentLevel = result.CurrentLevel };
+
+ if (result.LeveledUp)
+ {
+ _stampMessage = $"Chuc mung! Khach hang da len hang moi (Level {result.CurrentLevel})!";
+ }
+ else if (_stampCount == 0)
+ {
+ _stampMessage = "Chuc mung! Da tich du tem — 1 ly mien phi!";
+ }
+ else
+ {
+ _stampMessage = "Da tich 1 tem thanh cong!";
+ }
+ }
+ else
+ {
+ _stampMessage = "Khong the tich tem. Vui long thu lai.";
+ }
+ }
+ catch
+ {
+ _stampMessage = "Loi khi tich tem. Vui long thu lai.";
+ }
+ finally
+ {
+ _isAddingStamp = false;
+ }
}
}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/Workflow/MenuManagement.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/Workflow/MenuManagement.razor
index 3e177214..9710e727 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/Workflow/MenuManagement.razor
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Cafe/Workflow/MenuManagement.razor
@@ -5,7 +5,8 @@
@page "/pos/{ShopId:guid}/cafe/menu-management"
@layout PosLayout
@inherits PosBase
-@inject WebClientTpos.Client.Services.PosDataService DataService
+@using WebClientTpos.Client.Services
+@inject PosDataService DataService
@if (_isLoading)
@@ -28,7 +29,7 @@
Quản lý menu
Cập nhật tình trạng món — @_items.Count(i => i.Available) / @_items.Count có sẵn
-
+
@foreach (var cat in _filterCategories)
{
}
+ @* EN: Save Changes button / VI: Nút Lưu thay đổi *@
+
+ @if (_isSaving)
+ {
+ Đang lưu...
+ }
+ else
+ {
+
+ Lưu thay đổi
+ }
+
+ @if (!string.IsNullOrEmpty(_saveMessage))
+ {
+
+ @_saveMessage
+
+ }
@* EN: Menu items table / VI: Bảng danh sách món *@
@@ -80,7 +101,7 @@
@if (item.IsEditingPrice)
{
{ item.CurrentPrice = decimal.Parse(e.Value?.ToString() ?? "0"); item.IsEditingPrice = false; })" />
+ @onchange="@((ChangeEventArgs e) => { item.CurrentPrice = decimal.Parse(e.Value?.ToString() ?? "0"); item.IsEditingPrice = false; item.IsDirty = true; })" />
}
else
{
@@ -99,7 +120,7 @@
item.Available = !item.Available">
+ @onclick="() => ToggleAvailability(item)">
@@ -117,12 +138,20 @@
private bool _isLoading = true;
private bool _loadError;
+ // EN: Save state / VI: Trạng thái lưu
+ private bool _isSaving;
+ private string? _saveMessage;
+ private bool _saveIsError;
+
private string _filterCategory = "Tất cả";
private string[] _filterCategories = { "Tất cả" };
private IEnumerable
- @* EN: Status + timer / VI: Trạng thái + giờ *@
+ @* EN: Status + timer + F&B total / VI: Trang thai + gio + tong F&B *@
@GetStatusLabel(room.Status)
@@ -73,6 +73,12 @@
@((DateTime.Now - room.SessionStart.Value).ToString(@"h\:mm"))
}
+ @if (_roomFnbTotals.TryGetValue(room.Id, out var fnbTotal) && fnbTotal > 0)
+ {
+
+ F&B: @FormatPrice(fnbTotal)
+
+ }
@@ -107,9 +113,12 @@
private string _activeZone = "Tất cả";
private string[] _zones = { "Tất cả" };
- // EN: Room data loaded from DB / VI: Dữ liệu phòng tải từ DB
+ // EN: Room data loaded from DB / VI: Du lieu phong tai tu DB
private List
_rooms = new();
+ // EN: F&B totals per room / VI: Tong F&B theo phong
+ private Dictionary _roomFnbTotals = new();
+
private IEnumerable FilteredRooms =>
_activeZone == "Tất cả" ? _rooms : _rooms.Where(r => r.Zone == _activeZone);
@@ -128,11 +137,24 @@
t.Zone ?? "Standard",
t.Status,
t.Zone ?? "Tầng 1",
- t.StartedAt
+ t.StartedAt,
+ t.HourlyRate
)).ToList();
var zoneNames = _rooms.Select(r => r.Zone).Distinct().ToList();
_zones = new[] { "Tất cả" }.Concat(zoneNames).ToArray();
+
+ // EN: Load active orders to show F&B totals per room
+ // VI: Tai don hang de hien thi tong F&B theo phong
+ try
+ {
+ var orders = await DataService.GetActiveTableOrdersAsync(ShopId);
+ _roomFnbTotals = orders
+ .Where(o => o.TableId.HasValue)
+ .GroupBy(o => o.TableId!.Value.ToString())
+ .ToDictionary(g => g.Key, g => g.Sum(o => o.TotalAmount));
+ }
+ catch { /* EN: Non-critical, continue without F&B totals / VI: Khong quan trong, tiep tuc khong co tong F&B */ }
}
catch
{
@@ -144,7 +166,17 @@
}
}
- private void OpenRoom(RoomInfo room) => NavigateTo("karaoke/room-session");
+ // EN: Navigate based on room status — occupied rooms go to session, available rooms go to selection
+ // VI: Dieu huong theo trang thai — phong dang hat di den phien, phong trong di den chon phong
+ private void OpenRoom(RoomInfo room)
+ {
+ if (room.Status == "occupied")
+ NavigateTo($"karaoke/room-session/{room.Id}");
+ else if (room.Status == "available")
+ NavigateTo("karaoke/room-select");
+ else
+ NavigateTo($"karaoke/room-session/{room.Id}");
+ }
private static string GetStatusBg(string s) => s switch
{
@@ -171,5 +203,5 @@
"reserved" => "Đã đặt", "cleaning" => "Đang dọn", _ => s
};
- private record RoomInfo(string Id, string Name, int Capacity, string Type, string Status, string Zone, DateTime? SessionStart);
+ private record RoomInfo(string Id, string Name, int Capacity, string Type, string Status, string Zone, DateTime? SessionStart, decimal HourlyRate = 0);
}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeTablet.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeTablet.razor
index 6f8c5c9d..0bb4e380 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeTablet.razor
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeTablet.razor
@@ -97,16 +97,28 @@
- @* EN: Current F&B / VI: F&B hiện tại *@
+ @* EN: F&B orders from DB / VI: Đơn F&B từ DB *@
- @foreach (var item in _demoFnb)
+ @if (GetRoomOrders(_selectedRoom!.Id).Any())
{
-