2025-11-20 20:10:17 +03:30
|
|
|
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
|
|
|
|
|
using Google.Protobuf.WellKnownTypes;
|
|
|
|
|
|
2025-11-17 02:53:51 +03:30
|
|
|
namespace FrontOffice.Main.Utilities;
|
|
|
|
|
|
|
|
|
|
public record CartItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity)
|
|
|
|
|
{
|
|
|
|
|
public long LineTotal => UnitPrice * Quantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class CartService
|
|
|
|
|
{
|
2025-11-20 20:10:17 +03:30
|
|
|
private readonly ShopingCartContract.ShopingCartContractClient _client;
|
2025-11-17 02:53:51 +03:30
|
|
|
private readonly List<CartItem> _items = new();
|
|
|
|
|
public event Action? OnChange;
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
public CartService(ShopingCartContract.ShopingCartContractClient client)
|
|
|
|
|
{
|
|
|
|
|
_client = client;
|
|
|
|
|
_ = LoadFromServerAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 02:53:51 +03:30
|
|
|
public IReadOnlyList<CartItem> Items => _items.AsReadOnly();
|
|
|
|
|
public long Total => _items.Sum(i => i.LineTotal);
|
|
|
|
|
public int Count => _items.Sum(i => i.Quantity);
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
public async Task Add(Product product, int quantity = 1)
|
2025-11-17 02:53:51 +03:30
|
|
|
{
|
|
|
|
|
if (quantity <= 0) return;
|
|
|
|
|
var existing = _items.FirstOrDefault(i => i.ProductId == product.Id);
|
|
|
|
|
if (existing is null)
|
|
|
|
|
{
|
|
|
|
|
_items.Add(new CartItem(product.Id, product.Title, product.ImageUrl, product.Price, quantity));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var idx = _items.IndexOf(existing);
|
|
|
|
|
_items[idx] = existing with { Quantity = existing.Quantity + quantity };
|
|
|
|
|
}
|
|
|
|
|
Notify();
|
2025-11-20 20:10:17 +03:30
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _client.AddNewUserCartAsync(new AddNewUserCartRequest
|
|
|
|
|
{
|
|
|
|
|
ProductId = product.Id,
|
|
|
|
|
Count = quantity
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Best-effort sync with backend; keep local state on failure
|
|
|
|
|
}
|
2025-11-17 02:53:51 +03:30
|
|
|
}
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
public async Task UpdateQuantity(long productId, int quantity)
|
2025-11-17 02:53:51 +03:30
|
|
|
{
|
|
|
|
|
var existing = _items.FirstOrDefault(i => i.ProductId == productId);
|
|
|
|
|
if (existing is null) return;
|
|
|
|
|
if (quantity <= 0)
|
|
|
|
|
{
|
|
|
|
|
Remove(productId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var idx = _items.IndexOf(existing);
|
|
|
|
|
_items[idx] = existing with { Quantity = quantity };
|
|
|
|
|
Notify();
|
2025-11-20 20:10:17 +03:30
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
|
|
|
|
|
{
|
|
|
|
|
UserCartId = productId,
|
|
|
|
|
Count = quantity
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Best-effort sync with backend; keep local state on failure
|
|
|
|
|
}
|
2025-11-17 02:53:51 +03:30
|
|
|
}
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
public async Task Remove(long productId)
|
2025-11-17 02:53:51 +03:30
|
|
|
{
|
|
|
|
|
_items.RemoveAll(i => i.ProductId == productId);
|
|
|
|
|
Notify();
|
2025-11-20 20:10:17 +03:30
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Interpret Count=0 as remove from cart
|
|
|
|
|
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
|
|
|
|
|
{
|
|
|
|
|
UserCartId = productId,
|
|
|
|
|
Count = 0
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Best-effort sync with backend; keep local state on failure
|
|
|
|
|
}
|
2025-11-17 02:53:51 +03:30
|
|
|
}
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
public async Task Clear()
|
2025-11-17 02:53:51 +03:30
|
|
|
{
|
2025-11-20 20:10:17 +03:30
|
|
|
var productIds = _items.Select(i => i.ProductId).ToList();
|
2025-11-17 02:53:51 +03:30
|
|
|
_items.Clear();
|
|
|
|
|
Notify();
|
2025-11-20 20:10:17 +03:30
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (var id in productIds)
|
|
|
|
|
{
|
|
|
|
|
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
|
|
|
|
|
{
|
|
|
|
|
UserCartId = id,
|
|
|
|
|
Count = 0
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Best-effort sync with backend; keep local state on failure
|
|
|
|
|
}
|
2025-11-17 02:53:51 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Notify() => OnChange?.Invoke();
|
|
|
|
|
|
2025-11-20 20:10:17 +03:30
|
|
|
private async Task LoadFromServerAsync()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var response = await _client.GetAllUserCartAsync(new Empty());
|
|
|
|
|
_items.Clear();
|
|
|
|
|
foreach (var m in response.Models)
|
|
|
|
|
{
|
|
|
|
|
var item = new CartItem(
|
|
|
|
|
ProductId: m.Id,
|
|
|
|
|
Title: m.Title ?? string.Empty,
|
|
|
|
|
ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath,
|
|
|
|
|
UnitPrice: m.Price,
|
|
|
|
|
Quantity: m.SaleCount > 0 ? m.SaleCount : 1);
|
|
|
|
|
_items.Add(item);
|
|
|
|
|
}
|
|
|
|
|
Notify();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// If backend is unreachable or user is unauthenticated, fall back to local-only cart.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|