Leandro Hernan Rojas 20dd5d6943
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 11m36s
Add ProductForm Stock
2025-07-15 09:26:15 -03:00

262 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/stock/products"
@using Blazored.Typeahead
@using phronCare.UIBlazor.Services.Stock
@using phronCare.UIBlazor.Services.Lookups
@using Domain.Entities
@using Domain.Generics
@inject IToastService toastService
@inject NavigationManager Navigation
@inject LSProductService productService
@inject IStockLookUpService lookUpService
<div class="card" style="zoom:80%">
<div class="card-header d-flex justify-content-center align-items-center" style="zoom:80%;">
<h3 class="card-title m-0">Catálogo de Productos Médicos</h3>
</div>
<div class="card-body px-4">
<!-- FILTROS -->
<div class="mb-3 row g-2 align-items-end">
<div class="col-md-1">
<label for="code">Código</label>
<input id="code" class="form-control form-control-sm" style="height: 38px;" placeholder="Código interno o externo" @bind="SearchParams.Code" />
</div>
<div class="col-sm">
<label for="description">Nombre o descripción</label>
<input id="description" class="form-control form-control-sm" style="height: 38px;" placeholder="Nombre o descripción" @bind="SearchParams.Description" />
</div>
<div class="col-sm">
<label for="division">División</label>
<BlazoredTypeahead id="division" TItem="ELookUpItem" TValue="ELookUpItem"
SearchMethod="@(filter => lookUpService.GetProductDivisionsAsync(filter).ContinueWith(t => t.Result.AsEnumerable()))"
Value="_selectedDivision" ValueChanged="OnDivisionSelected"
ValueExpression="@(() => _selectedDivision)"
Placeholder="Buscar división..." TextProperty="Nombre" MaximumSuggestions="5"
class="form-control form-control-sm" style="height: 38px;">
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
</BlazoredTypeahead>
</div>
<div class="col-sm-2">
<label for="unitmeasure">Unidad</label>
<BlazoredTypeahead id="unitmeasure" TItem="ELookUpItem" TValue="ELookUpItem"
SearchMethod="@(filter => lookUpService.GetUnitsOfMeasureAsync(filter).ContinueWith(t => t.Result.AsEnumerable()))"
Value="_selectedUnit" ValueChanged="OnUnitSelected"
ValueExpression="@(() => _selectedUnit)"
Placeholder="Buscar unidad..." TextProperty="Nombre" MaximumSuggestions="5"
class="form-control form-control-sm" style="height: 38px;">
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
</BlazoredTypeahead>
</div>
<div class="col-sm-2">
<label for="type">Tipo</label>
<InputSelect id="type" class="form-select form-control-sm" style="height: 38px;" @bind-Value="SearchParams.ProductType">
<option value="">Tipo</option>
<option value="1">Implantable</option>
<option value="2">Instrumental</option>
<option value="3">Inyectable</option>
</InputSelect>
</div>
<div class="col-sm">
<label for="traceability">División</label>
<InputSelect id="traceability" class="form-select form-control-sm" style="height: 38px;" @bind-Value="SearchParams.TraceabilityType">
<option value="">Trazabilidad</option>
<option value="1">No aplica</option>
<option value="2">Por cantidad</option>
<option value="3">Por lote y vencimiento</option>
</InputSelect>
</div>
<div class="col-sm">
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input me-2" id="plusProcess" @bind="SearchParams.PlusProcess" />
<label class="form-check-label" for="plusProcess">Esterilizable</label>
</div>
</div>
</div>
<!-- BOONES DE BUSQUEDA -->
<div class="mb-3 d-flex justify-content-end gap-2">
<button class="btn btn-primary rounded-pill" @onclick="Buscar">
<i class="fas fa-binoculars me-1"></i> Buscar
</button>
<button class="btn btn-success rounded-pill" @onclick="NuevoProducto">
<i class="fas fa-plus me-1"></i> Nuevo
</button>
<button class="btn btn-warning rounded-pill" @onclick="ImportarProductos">
<i class="fas fa-file-upload me-1"></i> Importar
</button>
<button class="btn btn-success rounded-pill" @onclick="ExportarExcel">
<i class="fas fa-file-excel me-1"></i> Excel
</button>
<button class="btn btn-secondary rounded-pill" @onclick="Cancelar">
<i class="fas fa-arrow-left me-1"></i> Volver
</button>
</div>
<hr />
<div style="zoom:90%;">
@if (TablaProductos != null && TablaProductos.Any())
{
<PhTable Columns="TableColumns"
Data="TablaProductos"
SelectionField="Id"
RowsPerPage="SearchParams.PageSize"
RenderButtons="true"
Buttons="botones"
ShowPageButtons="false"
ShowQuickSearch="false"
RenderSelect="false" />
}
else
{
<p>No hay resultados.</p>
}
</div>
</div>
<div class="card-footer d-flex justify-content-center align-items-end" style="zoom:80%;">
<div class="d-flex align-items-center gap-3">
<button class="btn btn-secondary rounded-pill" @onclick="PrimeraPagina" disabled="@(SearchParams.Page == 1)">« Primera</button>
<button class="btn btn-secondary rounded-pill" @onclick="AnteriorPagina" disabled="@(!PuedeRetroceder)"> Anterior</button>
<span class="mx-2">Página <strong>@SearchParams.Page</strong> de <strong>@TotalPaginas</strong></span>
<button class="btn btn-secondary rounded-pill" @onclick="SiguientePagina" disabled="@(!PuedeAvanzar)">Siguiente </button>
<button class="btn btn-secondary rounded-pill" @onclick="UltimaPagina" disabled="@(SearchParams.Page == TotalPaginas)">Última »</button>
<input type="number" class="form-control form-control-sm rounded ms-3" style="width: 80px;" min="1" max="@TotalPaginas" @bind="PaginaDeseada" />
<button class="btn btn-outline-primary btn-sm rounded-pill ms-2" @onclick="IrAPagina">Ir</button>
</div>
</div>
</div>
@code {
private LSProductSearchParams SearchParams = new() { Page = 1, PageSize = 10 };
private List<Dictionary<string, object>> TablaProductos = new();
private PagedResult<ELSProduct>? PagedResult;
private int PaginaDeseada = 1;
private ELookUpItem? _selectedDivision;
private ELookUpItem? _selectedUnit;
List<PhTable.ButtonOptions> botones = new();
private List<string> TableColumns = new()
{
"Id", "Código Fábrica", "Código Externo", "Nombre", "Descripción", "División", "Unidad", "Tipo", "Trazabilidad", "Esteriliza"
};
protected override async Task OnInitializedAsync()
{
botones = new List<PhTable.ButtonOptions>
{
new PhTable.ButtonOptions
{
Caption = "Editar",
ElementClass = "btn btn-primary btn-sm",
UrlAction = "/stock/productform/",
OnClickAction = async (id) =>
{
if (int.TryParse(id, out var pid))
Navigation.NavigateTo($"/stock/productform/{pid}");
}
}
};
// await Buscar();
}
private async Task Buscar()
{
SearchParams.PageSize = 13;
SearchParams.Page = 1;
await CargarPaginaActual();
}
private async Task CargarPaginaActual()
{
SearchParams.DivisionId = _selectedDivision?.Id;
SearchParams.UnitId = _selectedUnit?.Id;
PagedResult = await productService.SearchAsync(SearchParams);
TablaProductos = PagedResult?.Items.Select(p => new Dictionary<string, object>
{
{ "Id", p.Id },
{ "Código Fábrica", p.FactoryCode },
{ "Código Externo", p.ExternalCode },
{ "Nombre", p.Name },
{ "Descripción", p.Descripcion },
{ "División", p.Division?.Name ?? "" },
{ "Unidad", p.Unit?.Name ?? "" },
{ "Tipo", ObtenerTipoProducto(p.ProductType) },
{ "Trazabilidad", ObtenerTipoTrazabilidad(p.TraceabilityType) },
{ "Esteriliza", p.PlusProcess ? "Sí" : "No" }
}).ToList() ?? [];
}
private void OnDivisionSelected(ELookUpItem item) => _selectedDivision = item;
private void OnUnitSelected(ELookUpItem item) => _selectedUnit = item;
private string ObtenerTipoProducto(int? tipo) => tipo switch
{
1 => "Implantable",
2 => "Instrumental",
3 => "Inyectable",
_ => ""
};
private string ObtenerTipoTrazabilidad(int? tipo) => tipo switch
{
1 => "No aplica",
2 => "Por cantidad",
3 => "Por lote/vencimiento",
_ => ""
};
private async Task PrimeraPagina() { SearchParams.Page = 1; await CargarPaginaActual(); }
private async Task UltimaPagina() { SearchParams.Page = TotalPaginas; await CargarPaginaActual(); }
private async Task SiguientePagina() => await CambiarPagina(1);
private async Task AnteriorPagina() => await CambiarPagina(-1);
private async Task CambiarPagina(int delta)
{
var nueva = SearchParams.Page + delta;
if (nueva >= 1 && nueva <= TotalPaginas)
{
SearchParams.Page = nueva;
await CargarPaginaActual();
}
}
private async Task IrAPagina()
{
if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas)
{
SearchParams.Page = PaginaDeseada;
await CargarPaginaActual();
}
else
{
toastService.ShowWarning("Página fuera de rango.");
}
}
private async Task ExportarExcel()
{
SearchParams.Page = 1;
SearchParams.PageSize = int.MaxValue; // Exportar todos los resultados
try
{
await productService.ExportFilteredAsync(SearchParams);
toastService.ShowSuccess("Exportación completada.");
}
catch (Exception ex)
{
toastService.ShowError($"Error: {ex.Message}");
}
}
private void NuevoProducto() => Navigation.NavigateTo("/stock/productform");
private void ImportarProductos() => Navigation.NavigateTo("/stock/productimport");
private void Cancelar() => Navigation.NavigateTo("/DashboardPanel");
private int TotalPaginas => PagedResult is null ? 1 : (int)Math.Ceiling(PagedResult.TotalItems / (double)SearchParams.PageSize);
private bool PuedeRetroceder => SearchParams.Page > 1;
private bool PuedeAvanzar => PagedResult != null && SearchParams.Page < TotalPaginas;
}