leandro e8f2e17820
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 16m10s
feat(sales): descargar PDF automáticamente al emitir Delivery Note y boton de impresion en consulta.
closes #43
2026-03-26 13:26:02 -03:00

277 lines
11 KiB
Plaintext

@page "/deliverynotes"
@using Domain.Dtos
@using Domain.Dtos.Sales
@using Domain.Generics
@using phronCare.UIBlazor.Services.Sales.DeliveryNotes
@inject NavigationManager Navigation
@inject IDeliveryNoteService deliveryNoteService
@inject IToastService toastService
<div class="card shadow-sm mb-3" style="zoom: 0.8;">
<div class="card-header py-2">
<div class="d-flex justify-content-center align-items-center">
<h3 class="card-title m-0">Consulta de Remitos</h3>
</div>
</div>
<div class="card-body pt-2 pb-0">
<div class="mb-3 row g-2 align-items-end">
<div class="col-sm">
<label for="deliveryNoteNumber">Remito</label>
<input id="deliveryNoteNumber" @bind="Filters.DeliveryNoteNumber" class="form-control form-control-sm" placeholder="DN-0000000X" />
</div>
<div class="col-sm">
<label for="customer">Cliente</label>
<input id="customer" @bind="Filters.CustomerText" class="form-control form-control-sm" placeholder="Nombre o código" />
</div>
<div class="col-sm">
<label for="quoteNumber">Presupuesto</label>
<input id="quoteNumber" @bind="Filters.QuoteNumber" class="form-control form-control-sm" placeholder="Q-0000000X" />
</div>
<div class="col-sm">
<label for="status">Estado</label>
<select id="status" @bind="Filters.Status" class="form-select form-select-sm">
<option value="">— Todos —</option>
<option value="Emitido">Emitido</option>
<option value="Anulado">Anulado</option>
<option value="Cerrado">Cerrado</option>
</select>
</div>
<div class="col-sm">
<label for="datefrom">Desde</label>
<InputDate id="datefrom" @bind-Value="Filters.IssueDateFrom" class="form-control form-control-sm" />
</div>
<div class="col-sm">
<label for="dateto">Hasta</label>
<InputDate id="dateto" @bind-Value="Filters.IssueDateTo" class="form-control form-control-sm" />
</div>
<div class="d-flex justify-content-end gap-2 mt-3">
<button class="btn btn-primary rounded-pill" @onclick="Search">
<i class="fas fa-binoculars me-1"></i> Buscar
</button>
<button class="btn btn-secondary rounded-pill ms-1" @onclick="OnClear">
<i class="fas fa-eraser me-1"></i> Limpiar
</button>
<button class="btn btn-success rounded-pill" @onclick="Create">
<i class="fas fa-plus me-1"></i> Nuevo
</button>
<button class="btn btn-success rounded-pill" @onclick="ExportarExcel">
<i class="fas fa-file-excel me-1"></i> Excel
</button>
</div>
</div>
</div>
</div>
<div class="card shadow-sm" style="zoom:0.8;">
<div class="table-responsive" style="zoom:0.8;">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th>Remito</th>
<th>Emisión</th>
<th>Presupuesto</th>
<th>Cliente</th>
<th>Estado</th>
<th>Observaciones</th>
<th>Reimpresiones</th>
<th style="width:80px;">Acciones</th>
</tr>
</thead>
<tbody>
@if (PagedDeliveryNotes?.Items?.Any() == true)
{
@foreach (var deliveryNote in PagedDeliveryNotes.Items)
{
<tr class="text-center">
<td>@deliveryNote.DeliveryNoteNumber</td>
<td>@deliveryNote.IssueDate.ToString("dd/MM/yyyy")</td>
<td>@(string.IsNullOrWhiteSpace(deliveryNote.QuoteNumber) ? "—" : deliveryNote.QuoteNumber)</td>
<td>@deliveryNote.CustomerName</td>
<td>
<span class="badge @GetStatusBadge(deliveryNote.Status)">@deliveryNote.Status</span>
</td>
<td>@(string.IsNullOrWhiteSpace(deliveryNote.Observations) ? "—" : deliveryNote.Observations)</td>
<td>@deliveryNote.PrintCount</td>
<td class="text-center align-middle">
<button class="btn btn-link btn-lg p-0 text-primary ms-2" title="Ver detalle" @onclick="() => OpenDetailAsync(deliveryNote)"><i class="fas fa-eye"></i></button>
<button class="btn btn-link btn-lg p-0 text-danger ms-2" title="Imprimir PDF" @onclick="() => PrintPdfAsync(deliveryNote)"><i class="fas fa-print"></i></button>
</td>
</tr>
}
}
else if (IsLoading)
{
<tr><td colspan="8" class="text-center text-muted py-4">Cargando...</td></tr>
}
else
{
<tr><td colspan="8" class="text-center text-muted py-4">Sin resultados</td></tr>
}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center align-items-center px-3 py-2 border-top">
<div class="btn-group justify-content-center">
<button class="btn btn-outline-secondary btn-sm rounded-pill" @onclick="PrimeraPagina" disabled="@(Filters.Page == 1)">Primera</button>
<button class="btn btn-outline-secondary btn-sm rounded-pill" @onclick="AnteriorPagina" disabled="@(!PuedeRetroceder)">Anterior</button>
<span class="mx-2">
Página <strong>@Filters.Page</strong> de <strong>@TotalPaginas</strong>
</span>
<button class="btn btn-outline-secondary btn-sm rounded-pill" @onclick="SiguientePagina" disabled="@(!PuedeAvanzar)">Siguiente</button>
<button class="btn btn-outline-secondary btn-sm rounded-pill" @onclick="UltimaPagina" disabled="@(Filters.Page == TotalPaginas)">Última</button>
<div class="d-flex align-items-center ms-3">
<input type="number"
class="form-control form-control-sm rounded"
style="width: 80px;"
min="1"
max="@TotalPaginas"
@bind="PaginaDeseada" />
</div>
<button class="btn btn-outline-secondary btn-sm rounded-pill" @onclick="IrAPagina">
<i class="fas fa-arrow-right-to-bracket me-1"></i> Ir
</button>
</div>
</div>
</div>
<DeliveryNoteDetailDrawer Summary="@SelectedSummary"
Detail="@SelectedDeliveryNote"
Visible="@IsDrawerOpen"
VisibleChanged="OnDrawerVisibleChanged"
Loading="@IsDetailLoading" />
@code {
private DeliveryNoteSearchParams Filters = new() { PageSize = 10 };
private PagedResult<DeliveryNoteSummaryDto>? PagedDeliveryNotes;
private DeliveryNoteSummaryDto? SelectedSummary;
private DeliveryNoteDto? SelectedDeliveryNote;
private bool IsDrawerOpen;
private bool IsDetailLoading;
private bool IsLoading;
private int PaginaDeseada = 1;
private async Task Search()
{
try
{
IsLoading = true;
PagedDeliveryNotes = await deliveryNoteService.SearchAsync(Filters);
PaginaDeseada = Filters.Page;
}
catch (Exception ex)
{
toastService.ShowError(ex.Message);
}
finally
{
IsLoading = false;
}
}
private async Task PrimeraPagina() { Filters.Page = 1; await Search(); }
private async Task UltimaPagina() { Filters.Page = TotalPaginas; await Search(); }
private async Task SiguientePagina() => await CambiarPagina(1);
private async Task AnteriorPagina() => await CambiarPagina(-1);
private async Task CambiarPagina(int delta)
{
var nuevaPagina = Filters.Page + delta;
if (nuevaPagina >= 1 && nuevaPagina <= TotalPaginas)
{
Filters.Page = nuevaPagina;
await Search();
}
}
private async Task IrAPagina()
{
if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas)
{
Filters.Page = PaginaDeseada;
await Search();
}
else
{
toastService.ShowWarning("Número de página fuera de rango.");
}
}
private bool PuedeRetroceder => PagedDeliveryNotes != null && Filters.Page > 1;
private bool PuedeAvanzar => PagedDeliveryNotes != null && Filters.Page < TotalPaginas;
private int TotalPaginas => PagedDeliveryNotes is null ? 1 :
(int)Math.Ceiling((double)(PagedDeliveryNotes.TotalItems) / Filters.PageSize);
private void Create()
{
Navigation.NavigateTo("/deliverynotes/create");
}
private void OnClear()
{
Filters = new DeliveryNoteSearchParams { PageSize = 10 };
PagedDeliveryNotes = null;
PaginaDeseada = 1;
}
private string GetStatusBadge(string status) => status switch
{
"Anulado" => "bg-danger text-white",
"Emitido" => "bg-primary text-white",
"Aprobado" => "bg-success",
"Cerrado" => "bg-dark text-white",
_ => "bg-light text-dark"
};
private async Task OpenDetailAsync(DeliveryNoteSummaryDto summary)
{
SelectedSummary = summary;
SelectedDeliveryNote = null;
IsDrawerOpen = true;
IsDetailLoading = true;
StateHasChanged();
try
{
var detail = await deliveryNoteService.GetByIdAsync(summary.Id);
if (detail is not null)
{
SelectedDeliveryNote = detail;
}
else
{
toastService.ShowError("No se pudo cargar el detalle del remito.");
}
}
catch (Exception ex)
{
toastService.ShowError(ex.Message);
}
finally
{
IsDetailLoading = false;
}
}
private Task OnDrawerVisibleChanged(bool visible)
{
IsDrawerOpen = visible;
return Task.CompletedTask;
}
private async Task PrintPdfAsync(DeliveryNoteSummaryDto deliveryNote)
{
try
{
await deliveryNoteService.ExportPdfAsync(deliveryNote.Id, deliveryNote.DeliveryNoteNumber);
}
catch (Exception ex)
{
toastService.ShowError(ex.Message);
}
}
private void ExportarExcel()
{
toastService.ShowInfo("La exportación a Excel se implementará en una próxima story.");
}
}