diff --git a/Domain/Entities/EPatient.cs b/Domain/Entities/EPatient.cs index e858c30..29271b6 100644 --- a/Domain/Entities/EPatient.cs +++ b/Domain/Entities/EPatient.cs @@ -8,10 +8,13 @@ namespace Domain.Entities [Required(ErrorMessage = "Debe ingresar un nombre.")] public string Firstname { get; set; } = null!; public string Lastname { get; set; } = null!; + [Required(ErrorMessage = "Debe ingresar un tipo de documento.")] public int? DocumenttypesId { get; set; } + [Required(ErrorMessage = "Debe ingresar un numero de documento.")] public string? DocumentNumber { get; set; } public string? AffiliateNumber { get; set; } public DateOnly? Birthdate { get; set; } + [Required(ErrorMessage = "Debe seleccionar un genero.")] public string? Gender { get; set; } public string? Phone { get; set; } public string? Email { get; set; } diff --git a/phronCare.API/Controllers/Sales/PatientController.cs b/phronCare.API/Controllers/Sales/PatientController.cs index 0dabb5b..398a460 100644 --- a/phronCare.API/Controllers/Sales/PatientController.cs +++ b/phronCare.API/Controllers/Sales/PatientController.cs @@ -90,7 +90,7 @@ namespace phronCare.API.Controllers.Sales catch (Exception ex) { var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); + return BadRequest($"Error de negocio: {ex.Message}"); } } @@ -112,7 +112,7 @@ namespace phronCare.API.Controllers.Sales catch (Exception ex) { var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); + return StatusCode(500, $"{ex.Message}"); } } diff --git a/phronCare.UIBlazor/Pages/Sales/CustomerForm.razor b/phronCare.UIBlazor/Pages/Sales/CustomerForm.razor index 7d2709c..32cb2d8 100644 --- a/phronCare.UIBlazor/Pages/Sales/CustomerForm.razor +++ b/phronCare.UIBlazor/Pages/Sales/CustomerForm.razor @@ -10,6 +10,7 @@ @inject AuthenticationStateProvider authenticationStateProvider @inject AccountTypeService accountTypeService @inject TaxConditionService taxConditionService +@inject DocumentTypeService documentTypeService
@@ -364,8 +365,7 @@ } private async Task LoadDocumentTypes() { - var result = await _httpClient.GetFromJsonAsync>("/api/DocumentType/GetAll"); - documentTypes = result ?? new(); + documentTypes = await documentTypeService.GetAllAsync(); } private void AddCustomerDocument() { diff --git a/phronCare.UIBlazor/Pages/Sales/PatientForm.razor b/phronCare.UIBlazor/Pages/Sales/PatientForm.razor new file mode 100644 index 0000000..ca92400 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/PatientForm.razor @@ -0,0 +1,184 @@ +@page "/sales/patientform" +@page "/sales/patientform/{PatientId:int?}" +@using System.ComponentModel.DataAnnotations +@using phronCare.UIBlazor.Services.Sales +@using phronCare.UIBlazor.Pages.Shared.Modals + +@inject PatientService patientService +@inject DocumentTypeService documentTypeService +@inject IToastService ToastService +@inject NavigationManager Navigation +@inject IModalService Modal + +
+
+

@((PatientId.HasValue ? "Editar paciente" : "Nuevo paciente"))

+
+
+ + + + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + + + @foreach (var type in documentTypes) + { + + } + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + + + + + + + +
+
+ + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+
+
+ + +
+ +@code { + [Parameter] public int? PatientId { get; set; } + [Parameter] public string? returnUrl { get; set; } = "/sales/patients"; + + private EPatient _model = new(); + private List documentTypes = new(); + private bool isSaving = false; + + protected override async Task OnInitializedAsync() + { + await LoadDocumentTypes(); + + if (PatientId.HasValue) + { + _model = await patientService.GetByIdAsync(PatientId.Value); + } + } + private async Task LoadDocumentTypes() + { + documentTypes = await documentTypeService.GetAllAsync(); + } + private async Task HandleValidSubmit() + { + var parameters = new ModalParameters(); + parameters.Add("Message", "¿Desea guardar los cambios del paciente?"); + var modal = Modal.Show("Confirmación", parameters); + var result = await modal.Result; + + if (result.Cancelled) + return; + + try + { + HttpResponseMessage response; + + if (_model.Id == 0) + response = await patientService.CreateAsync(_model); + else + response = await patientService.UpdateAsync(_model); + + if (response.IsSuccessStatusCode) + { + ToastService.ShowSuccess("Paciente guardado 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/patients"); + } +} diff --git a/phronCare.UIBlazor/Pages/Sales/Patients.razor b/phronCare.UIBlazor/Pages/Sales/Patients.razor new file mode 100644 index 0000000..79c5f48 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Patients.razor @@ -0,0 +1,186 @@ +@page "/sales/patients" +@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 PatientService patientService + +
+
+

Búsqueda de pacientes

+
+
+
+ + + + + + +
+
+
+ @if (TablaPacientes != null && TablaPacientes.Any()) + { + + } + else + { +

No hay resultados.

+ } +
+
+ +
+ +@code { + private PatientSearchParams SearchParams = new(); + private PagedResult? PagedResult; + private List> TablaPacientes = new(); + private List TableColumns = new() + { + "Id", "Nombre", "Apellido", "Documento", "#Socio | #Afiliado", "Género", "Teléfono", "Email" + }; + private int PaginaDeseada = 1; + + private List botones; + + protected override void OnInitialized() + { + botones = new List + { + new PhTable.ButtonOptions + { + Caption = "Editar", + ElementClass = "btn btn-primary btn-sm", + UrlAction = "/sales/patientform/", + OnClickAction = async (id) => + { + if (int.TryParse(id, out var pacienteId)) + { + Navigation.NavigateTo($"/sales/patientform/{pacienteId}"); + } + } + } + }; + } + + private async Task BuscarPacientes() => await CargarPacientes(); + + private async Task CargarPacientes() + { + PagedResult = await patientService.SearchPatientsAsync(SearchParams); + if (PagedResult?.Items is not null) + { + TablaPacientes = PagedResult.Items.Select(p => new Dictionary + { + { "Id", p.Id }, + { "Nombre", p.Firstname ?? string.Empty }, + { "Apellido", p.Lastname ?? string.Empty }, + { "Documento", $"{p.DocumenttypesId} {p.DocumentNumber}" }, + { "#Socio | #Afiliado", $"{p.AffiliateNumber}" }, + { "Género", p.Gender ?? string.Empty }, + { "Teléfono", p.Phone ?? string.Empty }, + { "Email", p.Email ?? string.Empty } + }).ToList(); + } + } + + private async Task PrimeraPagina() { SearchParams.Page = 1; await BuscarPacientes(); } + private async Task UltimaPagina() { SearchParams.Page = TotalPaginas; await BuscarPacientes(); } + 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 BuscarPacientes(); + } + } + + private async Task IrAPagina() + { + if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas) + { + SearchParams.Page = PaginaDeseada; + await BuscarPacientes(); + } + else + { + toastService.ShowWarning("Número de página fuera de rango."); + } + } + + private async Task ExportarExcel() + { + var searchParams = new PatientSearchParams + { + Name = SearchParams.Name, + Document = SearchParams.Document, + Page = 1, + PageSize = int.MaxValue + }; + try + { + await patientService.ExportFilteredAsync(searchParams); + toastService.ShowSuccess("Exportación completada exitosamente."); + } + catch (Exception ex) + { + toastService.ShowError($"{ex.Message}"); + } + } + + private void NuevoPaciente() => Navigation.NavigateTo("/sales/patientform/"); + 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); +} diff --git a/phronCare.UIBlazor/Program.cs b/phronCare.UIBlazor/Program.cs index bff4b3d..9530cd1 100644 --- a/phronCare.UIBlazor/Program.cs +++ b/phronCare.UIBlazor/Program.cs @@ -42,6 +42,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/phronCare.UIBlazor/Services/Sales/DocumentTypeService.cs b/phronCare.UIBlazor/Services/Sales/DocumentTypeService.cs new file mode 100644 index 0000000..d04d9dc --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/DocumentTypeService.cs @@ -0,0 +1,21 @@ +using Domain.Entities; +using System.Net.Http.Json; + +namespace phronCare.UIBlazor.Services.Sales +{ + public class DocumentTypeService + { + private readonly HttpClient _httpClient; + + public DocumentTypeService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task> GetAllAsync() + { + var result = await _httpClient.GetFromJsonAsync>("/api/DocumentType/GetAll"); + return result ?? new List(); + } + } +} diff --git a/phronCare.UIBlazor/Services/Sales/PatientService.cs b/phronCare.UIBlazor/Services/Sales/PatientService.cs new file mode 100644 index 0000000..8bdab0a --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/PatientService.cs @@ -0,0 +1,82 @@ +using Domain.Entities; +using Domain.Generics; +using Domain.SearchParams; +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 PatientService + { + private readonly HttpClient _http; + private readonly IJSRuntime _js; + + public PatientService(HttpClient http, IJSRuntime js) + { + _http = http; + _js = js; + } + + public async Task> GetAllAsync() + { + var result = await _http.GetFromJsonAsync>("/api/Patient/all"); + return result ?? new List(); + } + + public async Task GetByIdAsync(int id) + { + var result = await _http.GetFromJsonAsync($"/api/Patient/{id}"); + return result ?? new EPatient(); + } + + public async Task CreateAsync(EPatient patient) + { + return await _http.PostAsJsonAsync("/api/Patient/create", patient); + } + + public async Task UpdateAsync(EPatient patient) + { + return await _http.PutAsJsonAsync("/api/Patient/update", patient); + } + + public async Task?> SearchPatientsAsync(PatientSearchParams searchParams) + { + var url = $"api/Patient/search?" + + $"name={searchParams.Name}&" + + $"document={searchParams.Document}&" + + $"page={searchParams.Page}&" + + $"pageSize={searchParams.PageSize}"; + return await _http.GetFromJsonAsync>(url); + } + + public async Task ExportFilteredAsync(PatientSearchParams searchParams) + { + try + { + var content = new StringContent(JsonSerializer.Serialize(searchParams), Encoding.UTF8, "application/json"); + var response = await _http.PostAsync("api/Patient/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}_pacientes.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); + } + } + } +} diff --git a/phronCare.UIBlazor/Shared/NavMenu.razor b/phronCare.UIBlazor/Shared/NavMenu.razor index 4af9436..8b90e45 100644 --- a/phronCare.UIBlazor/Shared/NavMenu.razor +++ b/phronCare.UIBlazor/Shared/NavMenu.razor @@ -60,7 +60,7 @@ @if (!navMenuService.Minimized) { - + } @if (expClientes) @@ -68,12 +68,12 @@