fix: resolve POS duplicate products + settings shop name display

P2: Products appeared 2x in POS grid — BFF now filters isActive=true
by default, plus client-side dedup by product ID as safety net.
P3: Admin Settings showed "--" for shop name — parent ShopPage now
passes ShopName and VerticalLabel parameters to ShopSettings component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-13 21:26:08 +07:00
parent 5e666010b6
commit 344be332d7
3 changed files with 20 additions and 6 deletions

View File

@@ -252,7 +252,7 @@
break; break;
case "settings": case "settings":
<ShopSettings ShopId="@(_shopGuid ?? Guid.Empty)" /> <ShopSettings ShopId="@(_shopGuid ?? Guid.Empty)" ShopName="@_shopName" VerticalLabel="@_verticalLabel" />
break; break;
case "schedule": case "schedule":

View File

@@ -212,8 +212,19 @@ public class PosDataService
public async Task<ShopInfo?> GetShopByIdAsync(Guid shopId) public async Task<ShopInfo?> GetShopByIdAsync(Guid shopId)
=> await GetObjectFromApiAsync<ShopInfo>($"api/bff/shops/{shopId}"); => await GetObjectFromApiAsync<ShopInfo>($"api/bff/shops/{shopId}");
/// <summary>
/// EN: Get products for a shop. Deduplicates by product ID to prevent UI duplication
/// when the API response contains duplicate entries (e.g. from multi-format deserialization).
/// VI: Lấy sản phẩm của shop. Loại bỏ trùng lặp theo ID để tránh hiển thị đôi trên UI
/// khi API response chứa bản ghi trùng (ví dụ từ deserialization đa định dạng).
/// </summary>
public async Task<List<ProductInfo>> GetProductsAsync(Guid shopId) public async Task<List<ProductInfo>> GetProductsAsync(Guid shopId)
=> await GetListFromApiAsync<ProductInfo>($"api/bff/shops/{shopId}/products"); {
var products = await GetListFromApiAsync<ProductInfo>($"api/bff/shops/{shopId}/products");
// EN: Deduplicate by Id — prevents double-display regardless of API response shape.
// VI: Loại trùng theo Id — ngăn hiển thị đôi bất kể định dạng API response.
return products.GroupBy(p => p.Id).Select(g => g.First()).ToList();
}
public async Task<List<CategoryInfo>> GetCategoriesAsync(Guid shopId) public async Task<List<CategoryInfo>> GetCategoriesAsync(Guid shopId)
=> await GetListFromApiAsync<CategoryInfo>($"api/bff/shops/{shopId}/categories"); => await GetListFromApiAsync<CategoryInfo>($"api/bff/shops/{shopId}/categories");

View File

@@ -34,12 +34,15 @@ public class CatalogController : ControllerBase
} }
/// <summary> /// <summary>
/// EN: Get products for a specific shop. /// EN: Get products for a specific shop. POS-facing — only returns active products by default.
/// VI: Lấy sản phẩm của một cửa hàng cụ thể. /// VI: Lấy sản phẩm của một cửa hàng cụ thể. Dành cho POS — chỉ trả về sản phẩm đang hoạt động.
/// </summary> /// </summary>
[HttpGet("shops/{shopId}/products")] [HttpGet("shops/{shopId}/products")]
public Task<IActionResult> GetShopProducts(Guid shopId) => public Task<IActionResult> GetShopProducts(Guid shopId, [FromQuery] bool? isActive = true)
_catalog.GetAsync($"/api/v1/shops/{shopId}/products").ProxyAsync(); {
var qs = isActive.HasValue ? $"?isActive={isActive.Value.ToString().ToLower()}" : "";
return _catalog.GetAsync($"/api/v1/shops/{shopId}/products{qs}").ProxyAsync();
}
/// <summary> /// <summary>
/// EN: Create a product. /// EN: Create a product.