Compare commits

..

No commits in common. "319f7234c500f0753b692c0b30bb04329de39c3c" and "4ee5a99cb117ddde35b392346599467dd58d8b4d" have entirely different histories.

22 changed files with 33 additions and 743 deletions

3
.gitignore vendored
View File

@ -420,6 +420,3 @@ 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

View File

@ -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? type, string? province, int page = 1, int pageSize = 50);
Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50);
Task<bool> UpdateAsync(EInstitution entity);
}
}

View File

@ -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? type, string? province, int page = 1, int pageSize = 50)
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50)
{
try
{
return await _repository.SearchAsync(name, type, province, page, pageSize);
return await _repository.SearchAsync(name, city, province, page, pageSize);
}
catch (Exception ex)
{
@ -88,7 +88,7 @@ namespace Core.Services
{
var searchResult = await SearchAsync(
searchParams.Name,
searchParams.Type,
searchParams.City,
searchParams.Province,
searchParams.Page,
searchParams.PageSize

View File

@ -1,25 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Domain.Entities
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; }
[Required(ErrorMessage = "La provincia es obligatoria.")]
public string? Province { get; set; } = string.Empty;
public string? Province { get; set; }
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; } = 0;
public double? Longitude { get; set; } = 0;
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
}

View File

@ -3,7 +3,7 @@
public class InstitutionSearchParams :PagedRequest
{
public string? Name { get; set; }
public string? Type { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
}
}

View File

@ -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.2</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />

View File

@ -10,7 +10,7 @@ namespace Models.Interfaces
Task<EInstitution?> GetByIdAsync(int id);
Task<PagedResult<EInstitution>> SearchAsync(
string? name,
string? type,
string? city,
string? province,
int page = 1,
int pageSize = 50);

View File

@ -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? type, string? province, int page = 1, int pageSize = 50)
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, 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(type))
if (!string.IsNullOrWhiteSpace(city))
{
var lowered = type.ToLower();
query = query.Where(i => i.Type != null && i.Type.ToLower().Contains(lowered));
var lowered = city.ToLower();
query = query.Where(i => i.City != null && i.City.ToLower().Contains(lowered));
}
if (!string.IsNullOrWhiteSpace(province))

View File

@ -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.2</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />

View File

@ -33,14 +33,14 @@ namespace phronCare.API.Controllers.Sales
[HttpGet("search")]
public async Task<IActionResult> Search(
[FromQuery] string? name,
[FromQuery] string? type,
[FromQuery] string? city,
[FromQuery] string? province,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
try
{
var result = await _institutionService.SearchAsync(name, type, province, page, pageSize);
var result = await _institutionService.SearchAsync(name, city, province, page, pageSize);
return Ok(result);
}
catch (Exception ex)

View File

@ -664,7 +664,7 @@
"IsRequired": false
},
{
"Name": "type",
"Name": "city",
"Type": "System.String",
"IsRequired": false
},

View File

@ -1,195 +0,0 @@
@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");
}
}

View File

@ -1,190 +0,0 @@
@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);
}

View File

@ -44,7 +44,6 @@ 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>();

View File

@ -1,88 +0,0 @@
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);
}
}
}
}

View File

@ -2,25 +2,27 @@
@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>
<div class="circle-tile-content @item.MainColor">
<div class="circle-tile-description text-faded" style="color: @item.ValueColor;">
<div class="circle-tile-description text-faded" style="color: @item.ValueColor;">
<h5 style="text-transform: uppercase">@item.Title</h5>
@item.Description
</div>
<div class="circle-tile-number text-faded" style="color: @item.ValueColor;">
<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>
@ -34,26 +36,14 @@
</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>
</div>
@code {
[Parameter] public List<DashboardItem> Items { get; set; } = new List<DashboardItem>();
[Parameter] public string DashboardClass { get; set; } = string.Empty;
[Parameter]
public string DashboardClass { get; set; } = string.Empty;
private string cUrlAction = string.Empty;
public class DashboardItem
{
@ -64,15 +54,9 @@
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
}
}

View File

@ -1,73 +0,0 @@
@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");
}
}

View File

@ -81,11 +81,6 @@
<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>

View File

@ -1,24 +0,0 @@
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));
}
}
}
}

View File

@ -25,8 +25,4 @@
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\NewFolder\" />
</ItemGroup>
</Project>

View File

@ -1,59 +1,4 @@
<!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>
@ -87,4 +32,4 @@
<script src="js/services.js"></script>
</body>
</html>-->
</html>

View File

@ -1,51 +0,0 @@
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: '&copy; 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);
}
};