2 Commits

4 changed files with 264 additions and 140 deletions
Unified View
  1. BIN
      src/main/bundles/dev.bundle
  2. +71
    -57
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  3. +21
    -11
      src/main/java/mx/gob/jumapacelaya/models/Ticket.java
  4. +172
    -72
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java

BIN
src/main/bundles/dev.bundle View File


+ 71
- 57
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -3,6 +3,8 @@ package mx.gob.jumapacelaya.api;
import com.google.gson.*; import com.google.gson.*;
import mx.gob.jumapacelaya.models.RedmineUser; import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket; import mx.gob.jumapacelaya.models.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -12,6 +14,9 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,11 +26,11 @@ import java.util.Map;
@Component @Component
public class RedmineClient { public class RedmineClient {
private static final Logger log = LoggerFactory.getLogger(RedmineClient.class);
private static final int PAGE_SIZE = 25;
//private static final int PAGE_SIZE = 25;
static String REDMINE_URL; static String REDMINE_URL;
static String API_KEY; static String API_KEY;
public static final Gson GSON = new Gson();
public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) { public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) {
REDMINE_URL = redmineUrl; REDMINE_URL = redmineUrl;
@ -52,7 +57,9 @@ public class RedmineClient {
try { try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
tickets.addAll(parseTickets(response.body()));
String responseBody = response.body();
//log.info(responseBody);
tickets.addAll(parseTickets(responseBody));
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode()); System.err.println("Error en la respuesta: " + response.statusCode());
} }
@ -133,16 +140,13 @@ public class RedmineClient {
for (JsonElement issueElement : issues) { for (JsonElement issueElement : issues) {
JsonObject issue = issueElement.getAsJsonObject(); JsonObject issue = issueElement.getAsJsonObject();
// Verifica y obtiene el ID
// ID, subject, descripción
int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0; int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0;
// Verifica y obtiene el subject
String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : ""; String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : "";
// Verifica y obtiene la descripción
String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : ""; String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : "";
Double estimatedHrs = issue.has("estimated_hours") && !issue.get("estimated_hours").isJsonNull() ? issue.get("estimated_hours").getAsDouble() : 0.0;
// Verifica y obtiene el status
// Status
String status = "Unknown"; String status = "Unknown";
if (issue.has("status") && !issue.get("status").isJsonNull()) { if (issue.has("status") && !issue.get("status").isJsonNull()) {
JsonObject statusObject = issue.getAsJsonObject("status"); JsonObject statusObject = issue.getAsJsonObject("status");
@ -151,57 +155,21 @@ public class RedmineClient {
} }
} }
// Verifica y obtiene la fecha de creación
String dateString = issue.has("created_on") && !issue.get("created_on").isJsonNull() ? issue.get("created_on").getAsString() : "";
LocalDate dateCreate = null;
if (!dateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateCreate = LocalDate.parse(dateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha: " + dateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de cierre
String closeDateString = issue.has("closed_on") && !issue.get("closed_on").isJsonNull() ? issue.get("closed_on").getAsString() : "";
LocalDate dateClose = null;
if (!closeDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateClose = LocalDate.parse(closeDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de cierre: " + closeDateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de actualizacion
String updateDateString = issue.has("updated_on") && !issue.get("updated_on").isJsonNull() ? issue.get("updated_on").getAsString() : "";
LocalDate dateUpdate = null;
if (!updateDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateUpdate = LocalDate.parse(updateDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de actualización: " + updateDateString);
e.printStackTrace();
}
}
// Parse fechas correctamente
LocalDateTime dateCreate = parseDateTime(issue, "created_on");
LocalDateTime dateUpdate = parseDateTime(issue, "updated_on");
LocalDateTime dateClose = parseDateTime(issue, "closed_on");
// Autor
Ticket.User autor = null; Ticket.User autor = null;
if (issue.has("author") && !issue.get("author").isJsonNull()) { if (issue.has("author") && !issue.get("author").isJsonNull()) {
JsonObject authorObj = issue.getAsJsonObject("author"); JsonObject authorObj = issue.getAsJsonObject("author");
if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) { if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) {
String authorName = authorObj.get("name").getAsString();
autor = new Ticket.User(authorName);
autor = new Ticket.User(authorObj.get("name").getAsString());
} }
} }
//Verifica y obtiene el ID del tipo de ticket
// Tracker ID
Integer trackerId = null; Integer trackerId = null;
if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) { if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) {
JsonObject trackerObject = issue.getAsJsonObject("tracker"); JsonObject trackerObject = issue.getAsJsonObject("tracker");
@ -210,14 +178,26 @@ public class RedmineClient {
} }
} }
// Agrega el ticket a la lista
tickets.add(new Ticket(id, subject, description, status,
// Crear ticket
Ticket ticketObj = new Ticket(
id,
subject,
description,
status,
dateCreate != null ? dateCreate.toString() : "", dateCreate != null ? dateCreate.toString() : "",
dateClose != null ? dateClose.toString() : "", dateClose != null ? dateClose.toString() : "",
dateUpdate != null ? dateUpdate.toString() : "", dateUpdate != null ? dateUpdate.toString() : "",
autor, autor,
trackerId, "Tipo Desconocido"));
trackerId,
"Tipo Desconocido",
estimatedHrs
);
// Log para verificar JSON de ticket
//System.out.println(GSON.toJson(ticketObj));
tickets.add(ticketObj);
} }
} else { } else {
System.out.println("La respuesta JSON no contiene la clave 'issues'"); System.out.println("La respuesta JSON no contiene la clave 'issues'");
@ -229,6 +209,7 @@ public class RedmineClient {
return tickets; return tickets;
} }
public RedmineUser getMyAccount(String username) { public RedmineUser getMyAccount(String username) {
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/my/account.json")) .uri(URI.create(REDMINE_URL + "/my/account.json"))
@ -306,7 +287,7 @@ public class RedmineClient {
public RedmineUser getUserByUsername(String username) { public RedmineUser getUserByUsername(String username) {
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username ))
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("X-Redmine-API-Key", API_KEY) .header("X-Redmine-API-Key", API_KEY)
.build(); .build();
@ -369,4 +350,37 @@ public class RedmineClient {
System.err.println(response.body()); System.err.println(response.body());
} }
} }
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class,
(JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) ->
new JsonPrimitive(src.toString()))
.registerTypeAdapter(LocalDateTime.class,
(JsonDeserializer<LocalDateTime>) (json, typeOf, context) ->
LocalDateTime.parse(json.getAsString()))
.setPrettyPrinting()
.create();
// Método auxiliar para parsear echas con hora
private LocalDateTime parseDateTime(JsonObject issue, String fieldName) {
if (!issue.has(fieldName) || issue.get(fieldName).isJsonNull()) {
return null;
}
String dateStr = issue.get(fieldName).getAsString();
try {
// Si viene con 'Z' al final (UTC), convertir a LocalDateTime
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr);
}
} catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
return null;
}
}
} }

+ 21
- 11
src/main/java/mx/gob/jumapacelaya/models/Ticket.java View File

@ -5,6 +5,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class Ticket { public class Ticket {
private final int id; private final int id;
@ -17,12 +18,12 @@ public class Ticket {
private User autor; private User autor;
private Integer trackerId; private Integer trackerId;
private String type; private String type;
private Double estimatedHrs;
public Ticket(int id, String subject, String description, String status, public Ticket(int id, String subject, String description, String status,
String dateCreate, String dateClose, String updateOn, String dateCreate, String dateClose, String updateOn,
User autor, Integer trackerId, String type) {
User autor, Integer trackerId, String type, Double estimatedHrs) {
this.id = id; this.id = id;
this.subject = subject; this.subject = subject;
this.description = description; this.description = description;
@ -35,6 +36,7 @@ public class Ticket {
this.autor = autor; this.autor = autor;
this.trackerId = trackerId; this.trackerId = trackerId;
this.type = type; this.type = type;
this.estimatedHrs = estimatedHrs;
} }
@ -49,16 +51,16 @@ public class Ticket {
return null; return null;
} }
try { try {
OffsetDateTime odt = OffsetDateTime.parse(dateStr);
return odt.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
} catch (Exception e) {
try {
LocalDate ld = LocalDate.parse(dateStr);
return ld.atStartOfDay();
} catch (Exception ex) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
} }
} catch (Exception e) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
} }
} }
@ -123,6 +125,14 @@ public class Ticket {
return this.dateClose; return this.dateClose;
} }
public Double getEstimatedHrs() {
return estimatedHrs;
}
public void setEstimatedHrs(Double estimatedHrs) {
this.estimatedHrs = estimatedHrs;
}
public static class User { public static class User {
private String username; private String username;


+ 172
- 72
src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java View File

@ -11,6 +11,7 @@ import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3; import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
@ -25,8 +26,11 @@ import com.vaadin.flow.component.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.component.richtexteditor.RichTextEditor; import com.vaadin.flow.component.richtexteditor.RichTextEditor;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.api.RedmineClient; import mx.gob.jumapacelaya.api.RedmineClient;
import mx.gob.jumapacelaya.api.ServerProperties; import mx.gob.jumapacelaya.api.ServerProperties;
@ -36,8 +40,18 @@ import mx.gob.jumapacelaya.models.Ticket;
import mx.gob.jumapacelaya.services.LdapService; import mx.gob.jumapacelaya.services.LdapService;
import mx.gob.jumapacelaya.services.UserService; import mx.gob.jumapacelaya.services.UserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate; import java.time.LocalDate;
@ -54,13 +68,15 @@ import java.util.stream.Stream;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class ActDiariaView extends VerticalLayout { public class ActDiariaView extends VerticalLayout {
private static final Logger log = LoggerFactory.getLogger(ActDiariaView.class);
private final RedmineClient redmineClient; private final RedmineClient redmineClient;
private final UserService userService; private final UserService userService;
private final Grid<Ticket> grid; private final Grid<Ticket> grid;
private final HorizontalLayout showColumnsLyt; private final HorizontalLayout showColumnsLyt;
private final Button btnColumns; private final Button btnColumns;
private final Button btnExport;
private final HorizontalLayout opcionesLyt; private final HorizontalLayout opcionesLyt;
private final Checkbox chkVerCerrados;
private Checkbox chkSoloAbiertos;
private DatePicker fechaDesde; private DatePicker fechaDesde;
private DatePicker fechaHasta; private DatePicker fechaHasta;
private Button btnBuscar; private Button btnBuscar;
@ -72,7 +88,7 @@ public class ActDiariaView extends VerticalLayout {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
// Configuracion de las opciones de arriba
// Layout de opciones
opcionesLyt = new HorizontalLayout(); opcionesLyt = new HorizontalLayout();
opcionesLyt.setWidthFull(); opcionesLyt.setWidthFull();
opcionesLyt.setMargin(false); opcionesLyt.setMargin(false);
@ -83,26 +99,33 @@ public class ActDiariaView extends VerticalLayout {
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
// Checkbox para ver solo abiertos
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");
chkVerCerrados = new Checkbox();
chkVerCerrados.setLabel("Ver solo abiertos");
chkVerCerrados.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue().equals(false);
// Listener del checkbox
chkSoloAbiertos.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue(); // marcado = abiertos
loadTickets(soloAbiertos); loadTickets(soloAbiertos);
fechaDesde.setEnabled(false);
fechaHasta.setEnabled(false);
btnBuscar.setEnabled(false);
fechaDesde.setEnabled(!soloAbiertos);
fechaHasta.setEnabled(!soloAbiertos);
btnBuscar.setEnabled(!soloAbiertos);
}); });
fechaDesde = new DatePicker("Fecha desde:");
// Estado inicial
fechaDesde.setEnabled(true); fechaDesde.setEnabled(true);
fechaHasta = new DatePicker("Fecha hasta:");
fechaHasta.setEnabled(true); fechaHasta.setEnabled(true);
btnBuscar.setEnabled(true);
btnBuscar = new Button("Buscar");
opcionesLyt.add(chkVerCerrados,fechaDesde,fechaHasta,btnBuscar);
// Cargar tickets iniciales (cerrados)
loadTickets(false);
opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar);
// Configuración de columnas del grid // Configuración de columnas del grid
grid.addColumn(Ticket::getId).setHeader("No.") grid.addColumn(Ticket::getId).setHeader("No.")
@ -114,49 +137,54 @@ public class ActDiariaView extends VerticalLayout {
.setAutoWidth(true) .setAutoWidth(true)
.setKey("tipo"); .setKey("tipo");
grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())) grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId()))
.setHeader("Tiempo estimado") .setHeader("Tiempo estimado")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("tiempoEst"); .setKey("tiempoEst");
grid.addColumn(Ticket::getSubject)
.setHeader("Asunto")
.setWidth("25rem")
.setKey("asunto");
grid.addColumn(ticket ->
ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "")
.setHeader("Autor")
.setAutoWidth(true)
.setKey("autor");
grid.addColumn(createStatusRender()).setHeader("Estado")
.setKey("estado");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateCreate(); LocalDateTime fecha = ticket.getDateCreate();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha creación") }).setHeader("Fecha creación")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaCreacion"); .setKey("fechaCreacion");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateClose(); LocalDateTime fecha = ticket.getDateClose();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha cierre") }).setHeader("Fecha cierre")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaCierre"); .setKey("fechaCierre");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getUpdateOn(); LocalDateTime fecha = ticket.getUpdateOn();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha actualización") }).setHeader("Fecha actualización")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaActualizacion"); .setKey("fechaActualizacion");
grid.addColumn(ticket -> {
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
LocalDateTime inicio = ticket.getDateCreateLocal();
LocalDateTime fin = ticket.getDateCloseLocal();
long horasReales = ChronoUnit.HOURS.between(inicio, fin);
Double horasEstimadas = ticket.getEstimatedHrs();
if (horasEstimadas != null) {
return horasReales <= horasEstimadas ? "✅ En tiempo" : "⚠️ Excedido";
} else {
return "Sin estimación";
}
} else {
return "";
}
}).setHeader("Situación").setAutoWidth(true).setKey("cumplimientoHoras");
grid.addColumn(createStatusRender())
.setHeader("Estado")
.setKey("estado");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
if (ticket.getDateCreateLocal() != null) { if (ticket.getDateCreateLocal() != null) {
@ -172,6 +200,19 @@ public class ActDiariaView extends VerticalLayout {
}).setHeader("Duración").setAutoWidth(true).setKey("duracion"); }).setHeader("Duración").setAutoWidth(true).setKey("duracion");
grid.addColumn(ticket ->
ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "")
.setHeader("Autor")
.setAutoWidth(true)
.setKey("autor");
grid.addColumn(Ticket::getSubject)
.setHeader("Asunto")
.setWidth("25rem")
.setKey("asunto");
grid.addComponentColumn(ticket -> { grid.addComponentColumn(ticket -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE)); Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket)); btnVer.addClickListener(event -> showDescription(ticket));
@ -179,18 +220,18 @@ public class ActDiariaView extends VerticalLayout {
return btnVer; return btnVer;
}); });
//grid.addColumn(buttonTicketComponentRenderer()).setAutoWidth(true);
grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT); grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT);
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
//grid.getStyle().set("opacity", "0.8");
//grid.setAllRowsVisible(false);
grid.setSizeFull(); grid.setSizeFull();
// Mostrar boton para elegir que columnas mostrar
/**/btnColumns = new Button(VaadinIcon.GRID_H.create());
/**/showColumnsLyt = new HorizontalLayout(btnColumns);
// Botón para mostrar/ocultar columnas
btnColumns = new Button(VaadinIcon.GRID_H.create());
// Botón para exportar
btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create());
btnExport.addClickListener(e -> exportTicketsCerrados());
showColumnsLyt = new HorizontalLayout(btnExport, btnColumns);
showColumnsLyt.setJustifyContentMode(JustifyContentMode.END);
showColumnsLyt.setWidthFull();
HorizontalLayout columnsSelectorLyt = new HorizontalLayout(); HorizontalLayout columnsSelectorLyt = new HorizontalLayout();
columnsSelectorLyt.setAlignItems(Alignment.END); columnsSelectorLyt.setAlignItems(Alignment.END);
Popover popover = new Popover(); Popover popover = new Popover();
@ -220,46 +261,44 @@ public class ActDiariaView extends VerticalLayout {
} }
}); });
}); });
Set<String> defaultColumns = Set.of("tipo","asunto","autor","estado","fechaCreacion","fechaCierre");
Set<String> defaultColumns = Set.of("tipo","tiempoEst","fechaCreacion","fechaCierre","cumplimientoHoras","estado","duracion","autor","asunto");
chkColumns.setValue(defaultColumns); chkColumns.setValue(defaultColumns);
popover.add(heading, chkColumns); popover.add(heading, chkColumns);
/****/
// Ajustar tamaño del Grid y Layout
// Ajustar tamaños
grid.setSizeFull(); grid.setSizeFull();
setSizeFull(); setSizeFull();
add(opcionesLyt, showColumnsLyt, grid); add(opcionesLyt, showColumnsLyt, grid);
//expand(grid);
setMargin(false); setMargin(false);
loadTickets(true);
} }
// NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados // NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados
private void loadTickets(boolean soloAbiertos) { private void loadTickets(boolean soloAbiertos) {
RedmineUser user = userService.getRedmineUser(); RedmineUser user = userService.getRedmineUser();
// Invertir parámetro para que coincida con RedmineClient
boolean paramCliente = !soloAbiertos;
grid.setItems( grid.setItems(
query -> { query -> {
int offset = query.getOffset(); int offset = query.getOffset();
int limit = query.getLimit(); int limit = query.getLimit();
try { try {
List<Ticket> ticketsPage = redmineClient.getTickets(user, soloAbiertos, offset, limit);
List<Ticket> ticketsPage = redmineClient.getTickets(user, paramCliente, offset, limit);
return ticketsPage.stream(); return ticketsPage.stream();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return Stream.empty();
return Stream.empty();
} }
}, },
query -> {
return redmineClient.getTotalTickets(user, soloAbiertos);
}
query -> redmineClient.getTotalTickets(user, paramCliente)
); );
} }
private ComponentRenderer<Span, Ticket> createStatusRender() { private ComponentRenderer<Span, Ticket> createStatusRender() {
return new ComponentRenderer<>(ticket -> { return new ComponentRenderer<>(ticket -> {
// Creamos un Span para mostrar el estado // Creamos un Span para mostrar el estado
@ -267,27 +306,21 @@ public class ActDiariaView extends VerticalLayout {
// Estilos basados en el estado del ticket // Estilos basados en el estado del ticket
switch (ticket.getStatus().toLowerCase()) { switch (ticket.getStatus().toLowerCase()) {
case "análisis":
span.getElement().getStyle().set("color","orange");
case "rechazado":
String theme1 = String.format("badge %s", "error");
span.getElement().setAttribute("theme",theme1);
break; break;
case "desarrollo":
span.getElement().getStyle().set("color","blue");
break;
case "rechazada":
span.getElement().getStyle().set("color","red");
break;
case "cerrada":
span.getElement().getStyle().set("color","grey");
break;
case "solicitado":
span.getElement().getStyle().set("color","purple");
case "resuelto","cerrado":
String theme2 = String.format("badge %s", "success");
span.getElement().setAttribute("theme",theme2);
break; break;
case "qa": case "qa":
span.getElement().getStyle().set("color","#C21DF2");
String theme3 = String.format("badge %s", "warning");
span.getElement().setAttribute("theme",theme3);
break; break;
default: default:
span.getElement().getStyle().set("color","green");
break;
String theme4 = String.format("badge %s", "");
span.getElement().setAttribute("theme",theme4);
} }
return span; return span;
@ -353,4 +386,71 @@ public class ActDiariaView extends VerticalLayout {
dialog.open(); dialog.open();
} }
// Metodo para exportar los tickets cerrados a un archivo de Excel
private void exportTicketsCerrados() {
try {
RedmineUser user = userService.getRedmineUser();
List<Ticket> ticketsCerrados = redmineClient.getTickets(user, true, 0, 10000);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Tickets cerrados");
String[] headers = {"ID","Tipo","Estado","Autor","Asunto","Fecha Creación","Fecha Cierre","Fecha actualización","Duración","Situación"};
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[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.getStatus());
row.createCell(3).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "");
row.createCell(4).setCellValue(ticket.getSubject());
row.createCell(5).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : "");
row.createCell(6).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : "");
row.createCell(7).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : "");
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(8).setCellValue(dias + " dias");
} else {
row.createCell(8).setCellValue("");
}
if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(9).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido");
} else {
row.createCell(9).setCellValue("Sin estimación");
}
}
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
workbook.write(out);
workbook.close();
StreamResource resource = new StreamResource("tickets_cerrados.xlsx", () -> new ByteArrayInputStream(out.toByteArray()));
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry().registerResource(resource);
UI.getCurrent().getPage().executeJs("window.open('" + registration.getResourceUri().toString() + "','_blank')");
} catch (Exception e) {
log.error("Error exportando tickets", e);
Notification.show("Error al exportar los tickets", 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
}
} }

Loading…
Cancel
Save