Compare commits
2 Commits
4ee5a99cb1
...
319f7234c5
| Author | SHA1 | Date | |
|---|---|---|---|
| 319f7234c5 | |||
| ebabe96811 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -420,3 +420,6 @@ FodyWeavers.xsd
|
||||
*.tar
|
||||
/Core/obj/Core.csproj.nuget.g.props
|
||||
/Domain/obj/Domain.csproj.nuget.dgspec.json
|
||||
/Domain/obj/Domain.csproj.nuget.g.props
|
||||
/Models/obj/Models.csproj.nuget.g.props
|
||||
/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json
|
||||
|
||||
@ -15,7 +15,7 @@ namespace Core.Interfaces
|
||||
Task<byte[]> ExportFilteredInstitutionsToExcelAsync(InstitutionSearchParams searchParams);
|
||||
Task<PagedResult<EInstitution>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||
Task<EInstitution?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50);
|
||||
Task<PagedResult<EInstitution>> SearchAsync(string? name, string? type, string? province, int page = 1, int pageSize = 50);
|
||||
Task<bool> UpdateAsync(EInstitution entity);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,11 +66,11 @@ namespace Core.Services
|
||||
throw new ArgumentException("Debe seleccionar un tipo de institución.");
|
||||
return await _repository.UpdateAsync(entity);
|
||||
}
|
||||
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50)
|
||||
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? type, string? province, int page = 1, int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.SearchAsync(name, city, province, page, pageSize);
|
||||
return await _repository.SearchAsync(name, type, province, page, pageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -88,7 +88,7 @@ namespace Core.Services
|
||||
{
|
||||
var searchResult = await SearchAsync(
|
||||
searchParams.Name,
|
||||
searchParams.City,
|
||||
searchParams.Type,
|
||||
searchParams.Province,
|
||||
searchParams.Page,
|
||||
searchParams.PageSize
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
namespace Domain.Entities
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public partial class EInstitution
|
||||
{
|
||||
public int Id { get; set; }
|
||||
[Required(ErrorMessage = "El nombre de la institucion es obligatorio.")]
|
||||
public string Name { get; set; } = null!;
|
||||
[Required(ErrorMessage = "El tipo de institucion es obligatorio.")]
|
||||
public string Type { get; set; } = null!;
|
||||
public string? Streetaddress { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? Province { get; set; }
|
||||
[Required(ErrorMessage = "La provincia es obligatoria.")]
|
||||
public string? Province { get; set; } = string.Empty;
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Operatingroominfo { get; set; }
|
||||
public DateTime Createdat { get; set; }
|
||||
public bool Isactive { get; set; }
|
||||
public double? Latitude { get; set; }
|
||||
public double? Longitude { get; set; }
|
||||
public double? Latitude { get; set; } = 0;
|
||||
public double? Longitude { get; set; } = 0;
|
||||
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
public class InstitutionSearchParams :PagedRequest
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? Province { get; set; }
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
@ -10,7 +10,7 @@ namespace Models.Interfaces
|
||||
Task<EInstitution?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EInstitution>> SearchAsync(
|
||||
string? name,
|
||||
string? city,
|
||||
string? type,
|
||||
string? province,
|
||||
int page = 1,
|
||||
int pageSize = 50);
|
||||
|
||||
@ -33,7 +33,7 @@ namespace Models.Repositories
|
||||
var entity = await _context.PhSInstitutions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
|
||||
return entity == null ? null : EntityMapper.MapEntity<PhSInstitution, EInstitution>(entity);
|
||||
}
|
||||
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50)
|
||||
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? type, string? province, int page = 1, int pageSize = 50)
|
||||
{
|
||||
var query = _context.PhSInstitutions.AsQueryable();
|
||||
|
||||
@ -43,10 +43,10 @@ namespace Models.Repositories
|
||||
query = query.Where(i => i.Name.ToLower().Contains(lowered));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(city))
|
||||
if (!string.IsNullOrWhiteSpace(type))
|
||||
{
|
||||
var lowered = city.ToLower();
|
||||
query = query.Where(i => i.City != null && i.City.ToLower().Contains(lowered));
|
||||
var lowered = type.ToLower();
|
||||
query = query.Where(i => i.Type != null && i.Type.ToLower().Contains(lowered));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(province))
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
@ -33,14 +33,14 @@ namespace phronCare.API.Controllers.Sales
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> Search(
|
||||
[FromQuery] string? name,
|
||||
[FromQuery] string? city,
|
||||
[FromQuery] string? type,
|
||||
[FromQuery] string? province,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _institutionService.SearchAsync(name, city, province, page, pageSize);
|
||||
var result = await _institutionService.SearchAsync(name, type, province, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -664,7 +664,7 @@
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "city",
|
||||
"Name": "type",
|
||||
"Type": "System.String",
|
||||
"IsRequired": false
|
||||
},
|
||||
|
||||
195
phronCare.UIBlazor/Pages/Sales/InstitutionForm.razor
Normal file
195
phronCare.UIBlazor/Pages/Sales/InstitutionForm.razor
Normal file
@ -0,0 +1,195 @@
|
||||
@page "/sales/institutionform"
|
||||
@page "/sales/institutionform/{InstitutionId:int?}"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using phronCare.UIBlazor.Services.Sales
|
||||
@using phronCare.UIBlazor.Pages.Shared.Modals
|
||||
|
||||
@inject InstitutionService institutionService
|
||||
@inject IToastService ToastService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IModalService Modal
|
||||
|
||||
<div class="card" style="zoom:80%">
|
||||
<div class="card-header d-flex justify-content-center align-items-center">
|
||||
<h3 class="card-title m-0">@((InstitutionId.HasValue ? "Editar institución" : "Nueva institución"))</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<EditForm Model="_model">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<!-- Fila 1: Nombre de la institución -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<label for="Name">Nombre:</label>
|
||||
<InputText id="Name" @bind-Value="_model.Name" class="form-control" />
|
||||
<ValidationMessage For="@(() => _model.Name)" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="Type">Tipo de institución:</label>
|
||||
<InputSelect id="Type" @bind-Value="_model.Type" class="form-control">
|
||||
<option value="">--- Seleccionar ---</option>
|
||||
<option value="Sanatorio">Sanatorio</option>
|
||||
<option value="Clínica">Clínica</option>
|
||||
<option value="Hospital">Hospital</option>
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.Type)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Fila 3: Dirección -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="Streetaddress">Dirección:</label>
|
||||
<InputText id="Streetaddress" @bind-Value="_model.Streetaddress" class="form-control" />
|
||||
<ValidationMessage For="@(() => _model.Streetaddress)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fila 4: Ciudad, Provincia, Teléfono -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label for="City">Ciudad:</label>
|
||||
<InputText id="City" @bind-Value="_model.City" class="form-control" />
|
||||
<ValidationMessage For="@(() => _model.City)" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="Province">Provincia:</label>
|
||||
<InputSelect id="Province" @bind-Value="_model.Province" class="form-control">
|
||||
<option value="">--- Seleccionar ---</option>
|
||||
@foreach (var province in provinces)
|
||||
{
|
||||
<option value="@province">@province</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.Province)" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="Phone">Teléfono:</label>
|
||||
<InputText id="Phone" @bind-Value="_model.Phone" class="form-control" />
|
||||
<ValidationMessage For="@(() => _model.Phone)" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Fila 7: Mapas - Ubicación -->
|
||||
<div class="row mb-3">
|
||||
<div class="row">
|
||||
<!-- Columna izquierda: campos Latitud y Longitud -->
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="Email">Email:</label>
|
||||
<InputText id="Email" @bind-Value="_model.Email" class="form-control" />
|
||||
<ValidationMessage For="@(() => _model.Email)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Operatingroominfo">Información sobre quirófanos:</label>
|
||||
<InputTextArea id="Operatingroominfo" @bind-Value="_model.Operatingroominfo" class="form-control" rows="3" />
|
||||
<ValidationMessage For="@(() => _model.Operatingroominfo)" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="Latitude" class="form-label">Latitud</label>
|
||||
<InputNumber @bind-Value="_model.Latitude" class="form-control" id="Latitude" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="Longitude" class="form-label">Longitud</label>
|
||||
<InputNumber @bind-Value="_model.Longitude" class="form-control" id="Longitude" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna derecha: mapa -->
|
||||
<div class="col-md-6" style="zoom:80%">
|
||||
@if (_model.Latitude.HasValue && _model.Longitude.HasValue)
|
||||
{
|
||||
<PhMap Latitude="@_model.Latitude.Value"
|
||||
Longitude="@_model.Longitude.Value"
|
||||
Zoom="13"
|
||||
OnLocationChanged="HandleLocationChanged" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-muted">Ingrese coordenadas para visualizar el mapa</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end align-items-center py-3">
|
||||
<button class="btn btn-primary me-2" type="button" @onclick="HandleValidSubmit" disabled="@isSaving"> @(isSaving ? "Guardando..." : "Guardar institución") </button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="NavigateBack">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public int? InstitutionId { get; set; }
|
||||
[Parameter] public string? returnUrl { get; set; } = "/sales/institutions";
|
||||
private string searchQuery = string.Empty;
|
||||
|
||||
private EInstitution _model = new();
|
||||
private bool isSaving = false;
|
||||
private List<string> provinces = new()
|
||||
{
|
||||
"Buenos Aires", "CABA", "Catamarca", "Chaco", "Chubut", "Córdoba", "Corrientes",
|
||||
"Entre Ríos", "Formosa", "Jujuy", "La Pampa", "La Rioja", "Mendoza", "Misiones",
|
||||
"Neuquén", "Río Negro", "Salta", "San Juan", "San Luis", "Santa Cruz",
|
||||
"Santa Fe", "Santiago del Estero", "Tierra del Fuego", "Tucumán"
|
||||
};
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (InstitutionId.HasValue)
|
||||
{
|
||||
_model = await institutionService.GetByIdAsync(InstitutionId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleLocationChanged((double lat, double lng) location)
|
||||
{
|
||||
_model.Latitude = location.lat;
|
||||
_model.Longitude = location.lng;
|
||||
}
|
||||
|
||||
private async Task HandleValidSubmit()
|
||||
{
|
||||
var parameters = new ModalParameters();
|
||||
parameters.Add("Message", "¿Desea guardar los cambios de la institución?");
|
||||
var modal = Modal.Show<ConfirmModal>("Confirmación", parameters);
|
||||
var result = await modal.Result;
|
||||
|
||||
if (result.Cancelled)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
|
||||
if (_model.Id == 0)
|
||||
response = await institutionService.CreateAsync(_model);
|
||||
else
|
||||
response = await institutionService.UpdateAsync(_model);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
ToastService.ShowSuccess("Institución guardada correctamente.");
|
||||
NavigateBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
ToastService.ShowError($"Error: {error}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastService.ShowError($"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateBack()
|
||||
{
|
||||
Navigation.NavigateTo(returnUrl ?? "/sales/institutions");
|
||||
}
|
||||
}
|
||||
190
phronCare.UIBlazor/Pages/Sales/Institutions.razor
Normal file
190
phronCare.UIBlazor/Pages/Sales/Institutions.razor
Normal file
@ -0,0 +1,190 @@
|
||||
@page "/sales/institutions"
|
||||
@using phronCare.UIBlazor.Services.Sales
|
||||
@using phronCare.UIBlazor.Data
|
||||
@using Domain.Entities
|
||||
@using Domain.Generics
|
||||
@using Domain.SearchParams
|
||||
@inject IToastService toastService
|
||||
@inject NavigationManager Navigation
|
||||
@inject InstitutionService institutionService
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-center align-items-center" style="zoom:80%;">
|
||||
<h3 class="card-title m-0">Búsqueda de instituciones</h3>
|
||||
</div>
|
||||
<div class="card-body" style="zoom:80%;">
|
||||
<div class="mb-4 space-y-2">
|
||||
<input @bind="SearchParams.Name" placeholder="Nombre de la institución" class="border rounded p-1 w-full" />
|
||||
<input @bind="SearchParams.Type" placeholder="Tipo (Hospital, Clínica...)" class="border rounded p-1 w-full" />
|
||||
<input @bind="SearchParams.Province" placeholder="Provincia de la institución" class="border rounded p-1 w-full" />
|
||||
<button class="btn btn-primary rounded-pill" @onclick="BuscarInstituciones">
|
||||
<i class="fas fa-binoculars me-1"></i> Buscar
|
||||
</button>
|
||||
<button class="btn btn-success rounded-pill" @onclick="NuevaInstitucion">
|
||||
<i class="fas fa-plus me-1"></i> Nueva
|
||||
</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="Volver">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
@if (TablaInstituciones != null && TablaInstituciones.Any())
|
||||
{
|
||||
<PhTable Columns="TableColumns"
|
||||
Data="TablaInstituciones"
|
||||
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-center" 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)">
|
||||
<i class="fas fa-angle-double-left me-1"></i> Primera
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="AnteriorPagina" disabled="@(!PuedeRetroceder)">
|
||||
<i class="fas fa-chevron-left me-1"></i> 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 <i class="fas fa-chevron-right ms-1"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="UltimaPagina" disabled="@(SearchParams.Page == TotalPaginas)">
|
||||
Última <i class="fas fa-angle-double-right ms-1"></i>
|
||||
</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" />
|
||||
<button class="btn btn-outline-primary btn-sm ms-2 rounded-pill" @onclick="IrAPagina">
|
||||
<i class="fas fa-arrow-right-to-bracket me-1"></i> Ir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private InstitutionSearchParams SearchParams = new();
|
||||
private PagedResult<EInstitution>? PagedResult;
|
||||
private List<Dictionary<string, object>> TablaInstituciones = new();
|
||||
private List<string> TableColumns = new()
|
||||
{
|
||||
"Id", "Nombre", "Tipo", "Dirección", "Ciudad", "Provincia", "Teléfono", "Email", "Activo"
|
||||
};
|
||||
private int PaginaDeseada = 1;
|
||||
|
||||
private List<PhTable.ButtonOptions> botones;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
botones = new List<PhTable.ButtonOptions>
|
||||
{
|
||||
new PhTable.ButtonOptions
|
||||
{
|
||||
Caption = "Editar",
|
||||
ElementClass = "btn btn-primary btn-sm",
|
||||
UrlAction = "/sales/institutionform/",
|
||||
OnClickAction = async (id) =>
|
||||
{
|
||||
if (int.TryParse(id, out var instId))
|
||||
{
|
||||
Navigation.NavigateTo($"/sales/institutionform/{instId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task BuscarInstituciones() => await CargarInstituciones();
|
||||
|
||||
private async Task CargarInstituciones()
|
||||
{
|
||||
PagedResult = await institutionService.SearchInstitutionsAsync(SearchParams);
|
||||
if (PagedResult?.Items is not null)
|
||||
{
|
||||
TablaInstituciones = PagedResult.Items.Select(i => new Dictionary<string, object>
|
||||
{
|
||||
{ "Id", i.Id },
|
||||
{ "Nombre", i.Name ?? string.Empty },
|
||||
{ "Tipo", i.Type ?? string.Empty },
|
||||
{ "Dirección", i.Streetaddress ?? string.Empty },
|
||||
{ "Ciudad", i.City ?? string.Empty },
|
||||
{ "Provincia", i.Province ?? string.Empty },
|
||||
{ "Teléfono", i.Phone ?? string.Empty },
|
||||
{ "Email", i.Email ?? string.Empty },
|
||||
{ "Activo", i.Isactive ? "Sí" : "No" }
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PrimeraPagina() { SearchParams.Page = 1; await BuscarInstituciones(); }
|
||||
private async Task UltimaPagina() { SearchParams.Page = TotalPaginas; await BuscarInstituciones(); }
|
||||
private async Task SiguientePagina() => await CambiarPagina(1);
|
||||
private async Task AnteriorPagina() => await CambiarPagina(-1);
|
||||
|
||||
private async Task CambiarPagina(int delta)
|
||||
{
|
||||
var nuevaPagina = SearchParams.Page + delta;
|
||||
if (nuevaPagina >= 1 && nuevaPagina <= TotalPaginas)
|
||||
{
|
||||
SearchParams.Page = nuevaPagina;
|
||||
await BuscarInstituciones();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task IrAPagina()
|
||||
{
|
||||
if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas)
|
||||
{
|
||||
SearchParams.Page = PaginaDeseada;
|
||||
await BuscarInstituciones();
|
||||
}
|
||||
else
|
||||
{
|
||||
toastService.ShowWarning("Número de página fuera de rango.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportarExcel()
|
||||
{
|
||||
var exportParams = new InstitutionSearchParams
|
||||
{
|
||||
Name = SearchParams.Name,
|
||||
Type = SearchParams.Type,
|
||||
Province = SearchParams.Province,
|
||||
Page = 1,
|
||||
PageSize = int.MaxValue
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await institutionService.ExportFilteredAsync(exportParams);
|
||||
toastService.ShowSuccess("Exportación completada exitosamente.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toastService.ShowError($"{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void NuevaInstitucion() => Navigation.NavigateTo("/sales/institutionform/");
|
||||
private void Volver() => Navigation.NavigateTo("/DashboardPanel");
|
||||
|
||||
private bool PuedeRetroceder => PagedResult != null && SearchParams.Page > 1;
|
||||
private bool PuedeAvanzar => PagedResult != null && SearchParams.Page < TotalPaginas;
|
||||
private int TotalPaginas => PagedResult is null ? 1 :
|
||||
(int)Math.Ceiling((double)(PagedResult.TotalItems) / SearchParams.PageSize);
|
||||
}
|
||||
@ -44,6 +44,7 @@ builder.Services.AddScoped<TaxConditionService>();
|
||||
builder.Services.AddScoped<AccountTypeService>();
|
||||
builder.Services.AddScoped<PatientService>();
|
||||
builder.Services.AddScoped<DocumentTypeService>();
|
||||
builder.Services.AddScoped<InstitutionService>();
|
||||
builder.Services.AddScoped<ProductService>();
|
||||
builder.Services.AddScoped<BusinessUnitService>();
|
||||
builder.Services.AddScoped<ProductCategoryService>();
|
||||
|
||||
88
phronCare.UIBlazor/Services/Sales/InstitutionService.cs
Normal file
88
phronCare.UIBlazor/Services/Sales/InstitutionService.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Reflection;
|
||||
|
||||
namespace phronCare.UIBlazor.Services.Sales
|
||||
{
|
||||
public class InstitutionService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly IJSRuntime _js;
|
||||
|
||||
public InstitutionService(HttpClient http, IJSRuntime js)
|
||||
{
|
||||
_http = http;
|
||||
_js = js;
|
||||
}
|
||||
|
||||
// Obtener todas las instituciones
|
||||
public async Task<List<EInstitution>> GetAllAsync()
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<List<EInstitution>>("/api/Institution/all");
|
||||
return result ?? new List<EInstitution>();
|
||||
}
|
||||
|
||||
// Obtener institución por ID
|
||||
public async Task<EInstitution> GetByIdAsync(int id)
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<EInstitution>($"/api/Institution/{id}");
|
||||
return result ?? new EInstitution();
|
||||
}
|
||||
|
||||
// Crear una nueva institución
|
||||
public async Task<HttpResponseMessage> CreateAsync(EInstitution institution)
|
||||
{
|
||||
return await _http.PostAsJsonAsync("/api/Institution/create", institution);
|
||||
}
|
||||
|
||||
// Actualizar una institución existente
|
||||
public async Task<HttpResponseMessage> UpdateAsync(EInstitution institution)
|
||||
{
|
||||
return await _http.PutAsJsonAsync("/api/Institution/update", institution);
|
||||
}
|
||||
|
||||
// Buscar instituciones con parámetros de búsqueda (paginación)
|
||||
public async Task<PagedResult<EInstitution>?> SearchInstitutionsAsync(InstitutionSearchParams searchParams)
|
||||
{
|
||||
var url = $"api/Institution/search?" +
|
||||
$"name={searchParams.Name}&" +
|
||||
$"type={searchParams.Type}&" +
|
||||
$"province={searchParams.Province}&" +
|
||||
$"page={searchParams.Page}&" +
|
||||
$"pageSize={searchParams.PageSize}";
|
||||
return await _http.GetFromJsonAsync<PagedResult<EInstitution>>(url);
|
||||
}
|
||||
|
||||
// Exportar instituciones filtradas
|
||||
public async Task ExportFilteredAsync(InstitutionSearchParams searchParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(searchParams), Encoding.UTF8, "application/json");
|
||||
var response = await _http.PostAsync("api/Institution/exportfiltered", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception(errorContent);
|
||||
}
|
||||
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMddHHmm");
|
||||
var fileName = $"{timestamp}_instituciones.xlsx";
|
||||
await _js.InvokeVoidAsync("saveAsFile", fileName, base64);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
var message = ex.Message ?? "No message provided";
|
||||
throw new Exception($"{message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,13 +2,14 @@
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
@* <div class="row">
|
||||
@foreach (var item in Items)
|
||||
{
|
||||
<div class=@DashboardClass>
|
||||
<div class="circle-tile">
|
||||
<p>
|
||||
<div class="circle-tile-heading @item.MainColor"><br />
|
||||
<div class="circle-tile-heading @item.MainColor">
|
||||
<br />
|
||||
<i class="@item.Icon"></i>
|
||||
</div>
|
||||
</p>
|
||||
@ -20,9 +21,6 @@
|
||||
<div class="circle-tile-number text-faded" style="color: @item.ValueColor;">
|
||||
@item.Value
|
||||
</div>
|
||||
@* <!-- Utiliza el método NavigateToUrl para manejar la navegación -->
|
||||
<a class="circle-tile-footer" @onclick="() => NavigateToUrl(item.Url)">More Info <i class="fa fa-chevron-circle-right"></i></a>
|
||||
*@ <!-- RENDERIZAR FOOTER TIPO ONCLICK-->
|
||||
@if (item.OnClickAction is not null)
|
||||
{
|
||||
<a class="circle-tile-footer" @onclick="async () => await item.OnClickAction(item.OnClickParam)">More Info <i class="fa fa-chevron-circle-right"></i></a>
|
||||
@ -36,14 +34,26 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
*@
|
||||
<!-- MAPA DE EJEMPLO -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card shadow rounded-3">
|
||||
<div class="card-header bg-light">
|
||||
<strong>Ubicación de ejemplo</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<PhMap Latitude="-34.618998"
|
||||
Longitude="-58.501015" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public List<DashboardItem> Items { get; set; } = new List<DashboardItem>();
|
||||
[Parameter]
|
||||
public string DashboardClass { get; set; } = string.Empty;
|
||||
|
||||
private string cUrlAction = string.Empty;
|
||||
[Parameter] public string DashboardClass { get; set; } = string.Empty;
|
||||
|
||||
public class DashboardItem
|
||||
{
|
||||
@ -54,9 +64,15 @@
|
||||
public string MainColor { get; set; } = string.Empty;
|
||||
public string ValueColor { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public Func<string?,Task>? OnClickAction { get; set; }
|
||||
public Func<string?, Task>? OnClickAction { get; set; }
|
||||
public string? OnClickParam { get; set; }
|
||||
}
|
||||
|
||||
private void NavigateToUrl(string url) => Navigation.NavigateTo(url);
|
||||
|
||||
private void OnMapChanged((double Lat, double Lng) loc)
|
||||
{
|
||||
Console.WriteLine($"Nueva ubicación desde el mapa: {loc.Lat}, {loc.Lng}");
|
||||
// Aquí podrías guardar en una variable, mostrar en UI, o integrarlo con lógica posterior
|
||||
}
|
||||
}
|
||||
|
||||
73
phronCare.UIBlazor/Shared/Components/PhMap.razor
Normal file
73
phronCare.UIBlazor/Shared/Components/PhMap.razor
Normal file
@ -0,0 +1,73 @@
|
||||
@using phronCare.UIBlazor.Shared.Services
|
||||
@inject IJSRuntime JS
|
||||
|
||||
|
||||
<div class="card mt-2">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-map-marker-alt me-2"></i>Mapa de Ubicación</span>
|
||||
<div class="d-flex gap-2">
|
||||
@if (EnableAddressSearch)
|
||||
{
|
||||
<input @bind="SearchAddress"
|
||||
@bind:event="oninput"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="Buscar dirección..."
|
||||
style="width: 250px;" />
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="CenterByAddress" title="Buscar">
|
||||
<i class="fas fa-search-location"></i>
|
||||
</button>
|
||||
}
|
||||
<button class="btn btn-sm btn-outline-secondary" @onclick="OpenGoogleMaps" title="Abrir en Google Maps">
|
||||
<i class="fab fa-google me-1"></i>Ver en Google Maps
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0" style="height: 400px;">
|
||||
<div id="@MapDivId" style="height: 100%; width: 100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string MapDivId = $"map_{Guid.NewGuid()}";
|
||||
public static PhMap? CurrentInstance { get; private set; }
|
||||
|
||||
private double _latitude = -34.6037;
|
||||
private double _longitude = -58.3816;
|
||||
|
||||
[Parameter]
|
||||
public double Latitude
|
||||
{
|
||||
get => _latitude;
|
||||
set => _latitude = value;
|
||||
}
|
||||
[Parameter]
|
||||
public double Longitude
|
||||
{
|
||||
get => _longitude;
|
||||
set => _longitude = value;
|
||||
}
|
||||
[Parameter] public int Zoom { get; set; } = 13;
|
||||
[Parameter] public bool EnableAddressSearch { get; set; } = true;
|
||||
[Parameter] public EventCallback<(double lat, double lng)> OnLocationChanged { get; set; }
|
||||
|
||||
private string SearchAddress { get; set; } = string.Empty;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
MapInterop.RegisterInstance(this); // ✅ esto conecta con MapInterop.cs
|
||||
await Task.Delay(100);
|
||||
await JS.InvokeVoidAsync("phMap.initMap", MapDivId, Latitude, Longitude, Zoom);
|
||||
}
|
||||
}
|
||||
private async Task CenterByAddress()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SearchAddress)) await JS.InvokeVoidAsync("phMap.searchAddress", MapDivId, SearchAddress);
|
||||
}
|
||||
private void OpenGoogleMaps()
|
||||
{
|
||||
var url = $"https://www.google.com/maps?q={_latitude.ToString(System.Globalization.CultureInfo.InvariantCulture)},{_longitude.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
|
||||
JS.InvokeVoidAsync("window.open", url, "_blank");
|
||||
}
|
||||
}
|
||||
@ -81,6 +81,11 @@
|
||||
<li aria-hidden="true"></li> Productos
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-1">
|
||||
<NavLink class="nav-link" href="sales/institutions/">
|
||||
<li aria-hidden="true"></li> Instituciones
|
||||
</NavLink>
|
||||
</div>
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
24
phronCare.UIBlazor/Shared/Services/MapInterop.cs
Normal file
24
phronCare.UIBlazor/Shared/Services/MapInterop.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Microsoft.JSInterop;
|
||||
using phronCare.UIBlazor.Shared.Components;
|
||||
|
||||
namespace phronCare.UIBlazor.Shared.Services
|
||||
{
|
||||
public static class MapInterop
|
||||
{
|
||||
private static PhMap? CurrentInstance;
|
||||
public static void RegisterInstance(PhMap instance)
|
||||
{
|
||||
CurrentInstance = instance;
|
||||
}
|
||||
[JSInvokable("NotifyLocationChanged")]
|
||||
public static async Task NotifyLocationChanged(double lat, double lng)
|
||||
{
|
||||
if (CurrentInstance is not null)
|
||||
{
|
||||
CurrentInstance.Latitude = lat;
|
||||
CurrentInstance.Longitude = lng;
|
||||
await CurrentInstance.OnLocationChanged.InvokeAsync((lat, lng));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,4 +25,8 @@
|
||||
<ProjectReference Include="..\Domain\Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\NewFolder\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,4 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>phronCare.UIBlazor</title>
|
||||
<base href="/" />
|
||||
|
||||
<!-- Estilos propios -->
|
||||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="css/fontawesome-free-6.4.2-web/css/all.min.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="phronCare.UIBlazor.styles.css" rel="stylesheet" />
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
|
||||
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
|
||||
crossorigin="" />
|
||||
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
|
||||
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
|
||||
crossorigin=""></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<!-- Blazor y JS propios -->
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="css/fontawesome-free-6.4.2-web/js/all.min.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
|
||||
|
||||
<!-- Nuestro propio JS -->
|
||||
<script src="js/phmap.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<!--<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
@ -32,4 +87,4 @@
|
||||
<script src="js/services.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>-->
|
||||
|
||||
51
phronCare.UIBlazor/wwwroot/js/phmap.js
Normal file
51
phronCare.UIBlazor/wwwroot/js/phmap.js
Normal file
@ -0,0 +1,51 @@
|
||||
window.phMap = {
|
||||
initMap: function (mapId, lat, lng, zoom) {
|
||||
const map = L.map(mapId).setView([lat, lng], zoom);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
const marker = L.marker([lat, lng]).addTo(map)
|
||||
.bindPopup('Ubicación inicial')
|
||||
.openPopup();
|
||||
|
||||
// Guardamos el mapa y el marcador
|
||||
window._phMaps = window._phMaps || {};
|
||||
window._phMaps[mapId] = { map, marker };
|
||||
|
||||
map.on('click', function (e) {
|
||||
const newLat = e.latlng.lat;
|
||||
const newLng = e.latlng.lng;
|
||||
|
||||
// Mover el marcador existente
|
||||
window._phMaps[mapId].marker.setLatLng([newLat, newLng]);
|
||||
window._phMaps[mapId].marker.getPopup().setContent('Nueva ubicación').openOn(map);
|
||||
|
||||
// Llamar al método en Blazor
|
||||
DotNet.invokeMethodAsync('phronCare.UIBlazor', 'NotifyLocationChanged', newLat, newLng);
|
||||
});
|
||||
|
||||
},
|
||||
searchAddress: async function (mapId, address) {
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
alert("Dirección no encontrada.");
|
||||
return;
|
||||
}
|
||||
|
||||
const lat = parseFloat(data[0].lat);
|
||||
const lon = parseFloat(data[0].lon);
|
||||
|
||||
const mapData = window._phMaps[mapId];
|
||||
if (!mapData) return;
|
||||
|
||||
mapData.map.setView([lat, lon], 15);
|
||||
mapData.marker.setLatLng([lat, lon]);
|
||||
mapData.marker.getPopup().setContent(address.toUpperCase()).openOn(mapData.map);
|
||||
|
||||
DotNet.invokeMethodAsync('phronCare.UIBlazor', 'NotifyLocationChanged', lat, lon);
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user