ProductList

This commit is contained in:
Michał Zieliński
2025-07-17 14:29:02 +02:00
parent 518eff0ec7
commit 2a42f16daf
17 changed files with 397 additions and 38 deletions

View File

@@ -14,6 +14,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.17" />
<PackageReference Include="MudBlazor" Version="8.8.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
@@ -21,6 +23,10 @@
<ProjectReference Include="..\Bimix.Application\Bimix.Application.csproj" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Pages\ProductList.razor" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>

View File

@@ -1,5 +0,0 @@
@page "/products"
@using MudBlazor
<MudText Typo="Typo.h4">Produkty</MudText>
<p>Lista produktów zostanie tutaj zaimplementowana</p>

View File

@@ -0,0 +1,111 @@
@using MudBlazor.Internal
<MudText Typo="Typo.h4" Class="mb-4">Lista Produktów</MudText>
<MudExpansionPanels Class="mb-4">
<MudExpansionPanel Icon="@Icons.Material.Filled.FilterList"
Text="Filtry"
Expanded="true">
<MudGrid>
<MudItem xs="12" sm="6" md="4">
<MudTextField @bind-Value="filterRequest.Search"
Label="Szukaj"
Placeholder="Nazwa, Kod, EAN..."
Immediate="true"
DebounceInterval="500"
OnDebounceIntervalElapsed="SearchProducts"
Clearable="true"/>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudTextField @bind-Value="filterRequest.Name"
Label="Nazwa produktu"
Immediate="true"
DebounceInterval="500"
OnDebounceIntervalElapsed="SearchProducts"
Clearable="true"/>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudTextField @bind-Value="filterRequest.Code"
Label="Kod produktu"
Immediate="true"
DebounceInterval="500"
OnDebounceIntervalElapsed="SearchProducts"
Clearable="true"/>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudTextField @bind-Value="filterRequest.Ean"
Label="EAN"
Immediate="true"
DebounceInterval="500"
OnDebounceIntervalElapsed="SearchProducts"
Clearable="true">
</MudTextField>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudButton Variant="Variant.Outlined"
OnClick="ClearFilters"
StartIcon="Icons.Material.Filled.Clear">
Wyczyść filtry
</MudButton>
</MudItem>
</MudGrid>
</MudExpansionPanel>
</MudExpansionPanels>
<MudDivider Class="my-4"></MudDivider>
<MudTable Items="products.Items"
Dense="true"
Hover="true"
Loading="isLoading"
LoadingProgressColor="Color.Info">
<HeaderContent>
<MudTh>Nazwa</MudTh>
<MudTh>Kod</MudTh>
<MudTh>EAN</MudTh>
<MudTh>Akcje</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Nazwa">@context.Name</MudTd>
<MudTd DataLabel="Kod">@context.Code.Trim()</MudTd>
<MudTd DataLabel="EAN"></MudTd>
<MudTd DataLabel="Akcje">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
OnClick="() => EditProduct(context.Id)"/>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="Size.Small"
Color="Color.Error"
OnClick="() => DeleteProduct(context.Id)"/>
</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText>Brak produktów do wyświetlenia</MudText>
</NoRecordsContent>
<LoadingContent>
Ładowanie...
</LoadingContent>
</MudTable>
@if (products.TotalCount > 0)
{
<MudGrid Class="mt-4" AlignItems="Center.Center">
<MudItem xs="12" sm="6">
<MudText Typo="Typo.body2">
Wyniki @((products.Page - 1) * products.PageSize + 1) - @Math.Min(products.Page * products.PageSize, products.TotalCount)
z @products.TotalCount
</MudText>
</MudItem>
<MudItem xs="12" sm="6" Class="d-flex justify-end">
<MudPagination Count="products.TotalPages"
Selected="products.Page"
SelectedChanged="OnPageChanged"
ShowFirstButton="true"
ShowLastButton="true"/>
</MudItem>
</MudGrid>
}

View File

@@ -0,0 +1,77 @@
using Bimix.Application.DTOModels;
using Bimix.Application.DTOModels.Common;
using Bimix.Domain.Entities;
using Bimix.UI.Shared.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace Bimix.UI.Shared.Components;
public partial class ProductListComponent : ComponentBase
{
[Inject] private ProductService ProductService { get; set; } = default!;
private PagedResult<ProductDto> products = new();
private ProductFilterRequest filterRequest = new();
private bool isLoading = false;
protected override async Task OnInitializedAsync()
{
await LoadProducts();
}
private async Task StartBarcodeScanner()
{
}
private async Task LoadProducts()
{
isLoading = true;
try
{
products = await ProductService.GetProductsAsync(filterRequest);
}
catch (Exception ex)
{
Console.WriteLine($"Loading products failed: {ex.Message}");
}
finally
{
isLoading = false;
}
}
private async Task SearchProducts()
{
filterRequest.Page = 1;
await LoadProducts();
}
private async Task OnPageChanged(int page)
{
filterRequest.Page = page;
await LoadProducts();
}
private async Task ClearFilters()
{
filterRequest = new ProductFilterRequest();
await LoadProducts();
}
private async Task EditProduct(Guid productId)
{
// TODO
Console.WriteLine($"Edytuj produkt: {productId}");
}
private async Task DeleteProduct(Guid productId)
{
// TODO
Console.WriteLine($"Usuń produkt: {productId}");
}
}

View File

@@ -0,0 +1,17 @@
using Bimix.UI.Shared.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Bimix.UI.Shared.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddSharedServices(this IServiceCollection services, string apiBaseUrl)
{
services.AddHttpClient<ProductService>(client =>
{
client.BaseAddress = new Uri(apiBaseUrl);
});
return services;
}
}

View File

@@ -31,7 +31,7 @@
</MudDrawer>
<MudMainContent>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="my-4">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="my-4">
@Body
</MudContainer>
</MudMainContent>

View File

@@ -0,0 +1,7 @@
@page "/products"
<PageTitle>Produkty</PageTitle>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
<ProductListComponent />
</MudContainer>

View File

@@ -0,0 +1,52 @@
using System.Text.Json;
using Bimix.Application.DTOModels;
using Bimix.Application.DTOModels.Common;
using Microsoft.AspNetCore.WebUtilities;
namespace Bimix.UI.Shared.Services;
public class ProductService(HttpClient httpClient)
{
private readonly HttpClient _httpClient = httpClient;
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
public async Task<PagedResult<ProductDto>> GetProductsAsync(ProductFilterRequest request)
{
var queryParams = new Dictionary<string, string?>
{
["page"] = request.Page.ToString(),
["pageSize"] = request.PageSize.ToString(),
};
if (!string.IsNullOrWhiteSpace(request.Search))
{
queryParams["search"] = request.Search;
}
if (!string.IsNullOrWhiteSpace(request.Name))
{
queryParams["name"] = request.Name;
}
if (!string.IsNullOrWhiteSpace(request.Code))
{
queryParams["code"] = request.Code;
}
if (!string.IsNullOrWhiteSpace(request.Ean))
{
queryParams["ean"] = request.Ean;
}
var uri = QueryHelpers.AddQueryString("api/products", queryParams);
var response = await _httpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<PagedResult<ProductDto>>(json, _jsonOptions);
return result ?? new PagedResult<ProductDto>();
}
}