From cf6544619c59f57de0334e498ee1750cdd65d1cc Mon Sep 17 00:00:00 2001 From: mramirezg Date: Fri, 16 Jan 2026 14:49:17 -0600 Subject: [PATCH] Se corrigio el problema de que no se filtraban bien los tickets por fecha desde y hasta, asi como tambien se cambio a una forma mas vizual de cuando esta generando el excel con lo tickes en el perdiodo indicado. --- .../java/mx/gob/jumapacelaya/Application.java | 2 + .../gob/jumapacelaya/api/RedmineClient.java | 42 ++- .../mx/gob/jumapacelaya/ui/ActDiariaView.java | 252 +++++++++++------- src/main/resources/application.properties | 2 +- 4 files changed, 188 insertions(+), 110 deletions(-) diff --git a/src/main/java/mx/gob/jumapacelaya/Application.java b/src/main/java/mx/gob/jumapacelaya/Application.java index 252db23..3146686 100644 --- a/src/main/java/mx/gob/jumapacelaya/Application.java +++ b/src/main/java/mx/gob/jumapacelaya/Application.java @@ -1,6 +1,7 @@ package mx.gob.jumapacelaya; import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.component.page.Push; import com.vaadin.flow.server.PWA; import com.vaadin.flow.theme.Theme; import mx.gob.jumapacelaya.services.DatabaseService; @@ -17,6 +18,7 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura * */ @SpringBootApplication +@Push @Theme(value = "sistema-mantenimiento") @PWA(name = "Aplicacion de Mantenimiento de Equipo de Computo", shortName = "Mantenimiento de Computo", iconPath = "icons/icon.png") public class Application implements AppShellConfigurator { diff --git a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java index dd618d7..34af0bf 100644 --- a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java +++ b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java @@ -51,16 +51,29 @@ public class RedmineClient { StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() + statusFilter + "&offset=" + offset + "&limit=" + limit); + url.append("&sort=created_on:desc"); + // Filtro por fechas DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; - if (fechaDesde != null) { - url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter)); - } - if (fechaHasta != null) { - url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter)); + if (fechaDesde != null || fechaHasta != null) { + url.append("&created_on="); + + if (fechaDesde != null && fechaHasta != null) { + // En lugar de >< usamos %3E%3C y en lugar de | usamos %7C + url.append("%3E%3C") + .append(fechaDesde.toString()) + .append("%7C") + .append(fechaHasta.toString()); + } else if (fechaDesde != null) { + url.append("%3E%3D").append(fechaDesde.toString()); + } else { + url.append("%3C%3D").append(fechaHasta.toString()); + } } + //log.info("URL: " + url.toString()); + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url.toString())) .header("Content-Type", "application/json") @@ -95,11 +108,20 @@ public class RedmineClient { // Filtro por fechas DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; - if (fechaDesde != null) { - url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter)); - } - if (fechaHasta != null) { - url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter)); + if (fechaDesde != null || fechaHasta != null) { + url.append("&created_on="); + + if (fechaDesde != null && fechaHasta != null) { + // En lugar de >< usamos %3E%3C y en lugar de | usamos %7C + url.append("%3E%3C") + .append(fechaDesde.toString()) + .append("%7C") + .append(fechaHasta.toString()); + } else if (fechaDesde != null) { + url.append("%3E%3D").append(fechaDesde.toString()); + } else { + url.append("%3C%3D").append(fechaHasta.toString()); + } } HttpRequest request = HttpRequest.newBuilder() diff --git a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java index cdf546b..fbadb46 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java @@ -22,8 +22,10 @@ import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.page.Push; import com.vaadin.flow.component.popover.Popover; import com.vaadin.flow.component.popover.PopoverPosition; +import com.vaadin.flow.component.progressbar.ProgressBar; import com.vaadin.flow.component.richtexteditor.RichTextEditor; import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.function.SerializableBiConsumer; @@ -91,7 +93,8 @@ public class ActDiariaView extends VerticalLayout { // Layout de opciones opcionesLyt = new HorizontalLayout(); opcionesLyt.setWidthFull(); - opcionesLyt.setMargin(false); + opcionesLyt.setAlignItems(Alignment.BASELINE); + //opcionesLyt.setMargin(false); opcionesLyt.getStyle() .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") .set("border-radius", "10px") @@ -103,18 +106,20 @@ public class ActDiariaView extends VerticalLayout { chkSoloAbiertos = new Checkbox("Ver solo abiertos"); chkSoloAbiertos.setValue(false); // inicial: mostrar cerrados - fechaDesde = new DatePicker("Fecha desde:"); - fechaHasta = new DatePicker("Fecha hasta:"); - btnBuscar = new Button("Buscar"); + fechaDesde = new DatePicker(); + fechaDesde.setPlaceholder("Fecha desde:"); + fechaHasta = new DatePicker(); + fechaHasta.setPlaceholder("Fecha hasta:"); + btnBuscar = new Button("Buscar", VaadinIcon.SEARCH.create()); // Listener del checkbox chkSoloAbiertos.addValueChangeListener(e -> { boolean soloAbiertos = e.getValue(); // marcado = abiertos - loadTickets(soloAbiertos); - fechaDesde.setEnabled(!soloAbiertos); fechaHasta.setEnabled(!soloAbiertos); btnBuscar.setEnabled(!soloAbiertos); + + grid.getDataProvider().refreshAll(); }); // Estado inicial @@ -128,6 +133,30 @@ public class ActDiariaView extends VerticalLayout { opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar); + + grid.setItems( + query -> { + try { + return redmineClient.getTickets( + userService.getRedmineUser(), + chkSoloAbiertos.getValue(), + query.getOffset(), + query.getLimit(), + fechaDesde.getValue(), + fechaHasta.getValue() + ).stream(); + } catch (Exception e) { + log.error("Error al cargar tickets", e); + return Stream.empty(); + } + }, + query -> redmineClient.getTotalTickets( + userService.getRedmineUser(), + chkSoloAbiertos.getValue(), + fechaDesde.getValue(), + fechaHasta.getValue() + ) + ); // Configuración de columnas del grid grid.addColumn(Ticket::getId).setHeader("No.") .setAutoWidth(true) @@ -148,6 +177,7 @@ public class ActDiariaView extends VerticalLayout { return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; }).setHeader("Fecha creación") .setAutoWidth(true) + .setSortable(true) .setKey("fechaCreacion"); grid.addColumn(ticket -> { @@ -155,6 +185,7 @@ public class ActDiariaView extends VerticalLayout { return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; }).setHeader("Fecha cierre") .setAutoWidth(true) + .setSortable(true) .setKey("fechaCierre"); grid.addColumn(ticket -> { @@ -198,7 +229,7 @@ public class ActDiariaView extends VerticalLayout { } else { return ""; } - }).setHeader("Duración").setAutoWidth(true).setKey("duracion"); + }).setHeader("Duración").setAutoWidth(true).setSortable(true).setKey("duracion"); grid.addColumn(ticket -> @@ -229,6 +260,7 @@ public class ActDiariaView extends VerticalLayout { btnColumns = new Button(VaadinIcon.GRID_H.create()); // Botón para exportar btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create()); + btnExport.addThemeVariants(ButtonVariant.LUMO_PRIMARY); btnExport.addClickListener(e -> exportTicketsCerrados()); showColumnsLyt = new HorizontalLayout(btnExport, btnColumns); showColumnsLyt.setJustifyContentMode(JustifyContentMode.END); @@ -281,30 +313,6 @@ public class ActDiariaView extends VerticalLayout { // Invertir parámetro para que coincida con RedmineClient boolean paramCliente = !soloAbiertos; - - grid.setItems( - query -> { - try { - return redmineClient.getTickets( - user, - chkSoloAbiertos.getValue(), - query.getOffset(), - query.getLimit(), - fechaDesde.getValue(), - fechaHasta.getValue() - ).stream(); - } catch (Exception e) { - e.printStackTrace(); - return Stream.empty(); - } - }, - query -> redmineClient.getTotalTickets( - user, - chkSoloAbiertos.getValue(), - fechaDesde.getValue(), - fechaHasta.getValue() - ) - ); } @@ -399,85 +407,131 @@ public class ActDiariaView extends VerticalLayout { // Metodo para exportar los tickets cerrados a un archivo de Excel private void exportTicketsCerrados() { - try { - RedmineUser user = userService.getRedmineUser(); - - // Traer TODOS los tickets cerrados, respetando fechas si están seleccionadas - List ticketsCerrados = redmineClient.getAllTickets( - user, - false, // false = cerrados - fechaDesde.getValue(), - fechaHasta.getValue() - ); - - Workbook workbook = new XSSFWorkbook(); - Sheet sheet = workbook.createSheet("Tickets cerrados"); - - String[] headers = {"ID","Tipo","Tiempo estimado","Fecha creación","Fecha cierre", - "Fecha actualización","Situación","Estado","Duración","Autor","Asunto"}; - Row headerRow = sheet.createRow(0); - for (int i = 0; i < headers.length; i++) { - Cell cell = headerRow.createCell(i); - cell.setCellValue(headers[i]); - } + final UI ui = UI.getCurrent(); + final RedmineUser user = userService.getRedmineUser(); + final LocalDate desde = fechaDesde.getValue(); + final LocalDate hasta = fechaHasta.getValue(); - int rowNum = 1; - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); - - for (Ticket ticket : ticketsCerrados) { - Row row = sheet.createRow(rowNum++); - row.createCell(0).setCellValue(ticket.getId()); - row.createCell(1).setCellValue(ticket.getType()); - row.createCell(2).setCellValue(ticket.getEstimatedHrs() + " horas"); - row.createCell(3).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : ""); - row.createCell(4).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : ""); - row.createCell(5).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : ""); - row.createCell(7).setCellValue(ticket.getStatus()); - row.createCell(9).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : ""); - row.createCell(10).setCellValue(ticket.getSubject()); - - if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) { - long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal()); - row.createCell(8).setCellValue(dias + " días"); - } else { - row.createCell(8).setCellValue(""); - } + if (ui == null) return; - if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) { - long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal()); - row.createCell(6).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido"); - } else { - row.createCell(6).setCellValue("Sin estimación"); + Dialog loadingDialog = createLoadingDialog("Generando archivo por favor espere..."); + loadingDialog.open(); + + + new Thread(() -> { + try { + // Traer TODOS los tickets cerrados, respetando fechas si están seleccionadas + List ticketsCerrados = redmineClient.getAllTickets( + user, + false, // false = cerrados + desde, + hasta + ); + + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("Tickets cerrados"); + + String[] headers = {"ID","Tipo","Tiempo estimado","Fecha creación","Fecha cierre", + "Fecha actualización","Situación","Estado","Duración","Autor","Asunto"}; + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); } - } - for (int i = 0; i < headers.length; i++) { - sheet.autoSizeColumn(i); - } + int rowNum = 1; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); + + for (Ticket ticket : ticketsCerrados) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(ticket.getId()); + row.createCell(1).setCellValue(ticket.getType()); + row.createCell(2).setCellValue(ticket.getEstimatedHrs() + " horas"); + row.createCell(3).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : ""); + row.createCell(4).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : ""); + row.createCell(5).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : ""); + row.createCell(7).setCellValue(ticket.getStatus()); + row.createCell(9).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : ""); + row.createCell(10).setCellValue(ticket.getSubject()); + + if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) { + long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal()); + row.createCell(8).setCellValue(dias + " días"); + } else { + row.createCell(8).setCellValue(""); + } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - workbook.write(out); - workbook.close(); + if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) { + long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal()); + row.createCell(6).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido"); + } else { + row.createCell(6).setCellValue("Sin estimación"); + } + } - StreamResource resource = new StreamResource("tickets_cerrados.xlsx", - () -> new ByteArrayInputStream(out.toByteArray())); + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } - StreamRegistration registration = UI.getCurrent().getSession() - .getResourceRegistry().registerResource(resource); - UI.getCurrent().getPage().executeJs( - "window.open('" + registration.getResourceUri().toString() + "','_blank')" - ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + workbook.write(out); + workbook.close(); - } catch (Exception e) { - log.error("Error exportando tickets", e); - Notification.show("Error al exportar los tickets", 5000, Notification.Position.MIDDLE) - .addThemeVariants(NotificationVariant.LUMO_ERROR); - } + ui.access(() -> { + try { + StreamResource resource = new StreamResource("rptGeneralDeTicketsMsaAyda.xlsx", + () -> new ByteArrayInputStream(out.toByteArray())); + + StreamRegistration registration = ui.getSession() + .getResourceRegistry().registerResource(resource); + + ui.getPage().executeJs( + "window.open('" + registration.getResourceUri().toString() + "','_blank')" + ); + } catch (Exception ex) { + log.error("Error al registrar recurso", ex); + } finally { + loadingDialog.close(); + } + }); + + } catch (Exception e) { + log.error("Error exportando tickets", e); + ui.access(() -> { + loadingDialog.close(); + Notification.show("Error al generar el reporte", 5000, Notification.Position.MIDDLE) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + }); + } + }).start(); } private void filtrarTickets() { - boolean soloAbiertos = chkSoloAbiertos.getValue(); - loadTickets(soloAbiertos); + grid.getDataProvider().refreshAll(); + } + + private Dialog createLoadingDialog(String mensaje) { + Dialog dialog = new Dialog(); + dialog.setCloseOnEsc(false); + dialog.setCloseOnOutsideClick(false); + + VerticalLayout layout = new VerticalLayout(); + layout.setAlignItems(Alignment.CENTER); + layout.setPadding(true); + layout.setSpacing(true); + + ProgressBar progressBar = new ProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setWidth("15rem"); + + Span texto = new Span(mensaje); + texto.getStyle().set("font-weight", "bold"); + texto.getStyle().set("color", "var(--lumo-primary-text-color)"); + + layout.add(texto, progressBar); + dialog.add(layout); + + return dialog; } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f4b9471..7589b76 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,7 +18,7 @@ spring.ldap.username=administrator spring.ldap.password=Dr3na$134%4guA ###################PRODUCTIVO#################### -redmine.url=https://proyman.jumapacelaya.gob.mx/ +redmine.url=https://proyman.jumapacelaya.gob.mx redmine.api_key=69be2a5df9bacce02722f566fdf0731d728a1b86