feat: smart product autocomplete for inventory dialogs
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m14s

- DiscountProductsAutoComplete: new autocomplete component for discount
  products with search/debounce (mirrors ProductsAutoComplete pattern)
- AddStockDialog: replace manual product ID input with smart autocomplete
  - When 'regular product' selected → shows ProductsAutoComplete
  - When 'discount product' selected → shows DiscountProductsAutoComplete
  - Admin never has to type product IDs manually
  - Product type selector controls which autocomplete is shown
- Validates product selection before submit
This commit is contained in:
masoodafar-web
2026-02-18 00:43:12 +03:30
parent 6147499f9c
commit c9affe62bd
3 changed files with 126 additions and 10 deletions
@@ -0,0 +1,14 @@
@using CMSMicroservice.Protobuf.Protos.DiscountProduct
<MudAutocomplete T="DiscountProductDto"
Label="@Label"
Value="@_item"
DebounceInterval="700"
ValueChanged="@((e) => OnSelected(e))"
ToStringFunc="@(e => e == null ? null : e.Title)"
SearchFunc="@Search"
Variant="Variant.Outlined"
Clearable="true"
ShowProgressIndicator="true"
CoerceText="false"
OnClearButtonClick="()=> OnSelected(null)" />
@@ -0,0 +1,83 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using Microsoft.AspNetCore.Components;
namespace BackOffice.Pages.AutoComplete;
public partial class DiscountProductsAutoComplete
{
[Inject] public DiscountProductContract.DiscountProductContractClient DiscountProductService { get; set; } = default!;
[Parameter] public long? Value { get; set; }
[Parameter] public string? Label { get; set; } = "محصول تخفیفی";
[Parameter] public EventCallback<long?> ValueChanged { get; set; }
private List<DiscountProductDto> _items = new();
private DiscountProductDto? _item;
protected override async void OnParametersSet()
{
await base.OnParametersSetAsync();
if (Value.HasValue && Value.Value > 0 && (_item == null || _item.Id != Value.Value))
{
try
{
var getAll = await DiscountProductService.GetDiscountProductsAsync(new GetDiscountProductsRequest
{
PageNumber = 1,
PageSize = 50
});
if (getAll?.Models != null)
{
_item = getAll.Models.FirstOrDefault(x => x.Id == Value.Value);
StateHasChanged();
}
}
catch
{
// Silently handle
}
}
}
private async Task OnSelected(DiscountProductDto? model)
{
if (model == null)
{
_item = null;
await ValueChanged.InvokeAsync(null);
}
else
{
_item = model;
await ValueChanged.InvokeAsync(model.Id);
}
}
private async Task<IEnumerable<DiscountProductDto>> Search(string value, CancellationToken token)
{
var request = new GetDiscountProductsRequest
{
PageNumber = 1,
PageSize = 9
};
if (!string.IsNullOrWhiteSpace(value))
{
request.SearchQuery = value;
}
try
{
var getAll = await DiscountProductService.GetDiscountProductsAsync(request);
_items = getAll?.Models?.ToList() ?? new List<DiscountProductDto>();
}
catch
{
_items = new List<DiscountProductDto>();
}
return _items;
}
}
@@ -1,6 +1,7 @@
@using CMSMicroservice.Protobuf.Protos.Inventory
@using Google.Protobuf.WellKnownTypes
@using InventoryProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType
@using BackOffice.Pages.AutoComplete
<MudDialog>
<DialogContent>
@@ -19,12 +20,16 @@
</MudSelect>
</MudItem>
<MudItem xs="12">
<MudNumericField T="long" @bind-Value="_productId"
Label="شناسه محصول"
Variant="Variant.Outlined"
Required="true"
RequiredError="شناسه محصول الزامی است"
Min="1" />
@if (_productType == InventoryProductType.RegularProduct)
{
<ProductsAutoComplete @bind-Value="_productId"
Label="انتخاب محصول عادی" />
}
else
{
<DiscountProductsAutoComplete @bind-Value="_discountProductId"
Label="انتخاب محصول تخفیفی" />
}
</MudItem>
}
else
@@ -103,7 +108,8 @@
private bool _isSubmitting;
private InventoryProductType _productType = InventoryProductType.RegularProduct;
private long _productId;
private long? _productId;
private long? _discountProductId;
private long? _warehouseId;
private int _quantity = 1;
private string _referenceNumber = string.Empty;
@@ -111,9 +117,12 @@
protected override void OnInitialized()
{
if (ProductIdParam.HasValue)
if (ProductIdParam.HasValue && ProductTypeParam.HasValue)
{
_productId = ProductIdParam.Value;
if (ProductTypeParam.Value == InventoryProductType.DiscountProduct)
_discountProductId = ProductIdParam.Value;
else
_productId = ProductIdParam.Value;
}
if (ProductTypeParam.HasValue)
{
@@ -144,9 +153,19 @@
_isSubmitting = true;
try
{
var selectedProductId = ProductIdParam
?? (_productType == InventoryProductType.DiscountProduct ? _discountProductId : _productId);
if (!selectedProductId.HasValue || selectedProductId.Value <= 0)
{
Snackbar.Add("لطفاً یک محصول انتخاب کنید", Severity.Warning);
_isSubmitting = false;
return;
}
var request = new AddStockRequest
{
ProductId = ProductIdParam ?? _productId,
ProductId = selectedProductId.Value,
ProductType = ProductTypeParam ?? _productType,
Quantity = _quantity,
ReferenceNumber = _referenceNumber ?? string.Empty,