All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 16m10s
closes #43
277 lines
11 KiB
Plaintext
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.");
|
|
}
|
|
}
|