diff --git a/src/main/bundles/dev.bundle b/src/main/bundles/dev.bundle index bd01910..4d9b9ce 100644 Binary files a/src/main/bundles/dev.bundle and b/src/main/bundles/dev.bundle differ diff --git a/src/main/frontend/themes/sistema-mantenimiento/styles.css b/src/main/frontend/themes/sistema-mantenimiento/styles.css index c806e58..69e96c4 100644 --- a/src/main/frontend/themes/sistema-mantenimiento/styles.css +++ b/src/main/frontend/themes/sistema-mantenimiento/styles.css @@ -115,6 +115,29 @@ vaadin-month-calendar::part(date) { color: #691b31; /* Color para los dias del calendario */ } +vaadin-button { + border-radius: 10px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(105, 27, 49, 0.28), 0 2px 8px rgba(0, 0, 0, 0.13); + transition: box-shadow 0.2s, border 0.2s; +} + +vaadin-button::after { + content: ""; + position: absolute; + inset: 0; + background: rgba(240,223,223,0.2); + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; +} + +vaadin-button:hover::after { + box-shadow: 0 16px 36px rgba(105,27,49,0.35), 0 4px 16px rgba(0,0,0,0.18); + border-color: #BC955B; + opacity: 1; +} + vaadin-month-calendar::part(date):hover /* Estilos para cuando se posiciona el puntero sobre el dia */{ background-color: #a02142; opacity: 50%; @@ -192,8 +215,7 @@ vaadin-side-nav-item::part(content) { /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /*Estilos especificos para el Grid */ vaadin-grid::part(selected-row-cell) { - background-color: #ddc9a3; - opacity: 100%; + background-color: rgba(221, 201, 163, 0.7); } vaadin-grid::part(selected-row) { diff --git a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java index cf24a25..dd618d7 100644 --- a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java +++ b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java @@ -3,15 +3,19 @@ package mx.gob.jumapacelaya.api; import com.google.gson.*; import mx.gob.jumapacelaya.models.RedmineUser; import mx.gob.jumapacelaya.models.Ticket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; - import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; 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.DateTimeParseException; import java.util.ArrayList; @@ -21,11 +25,11 @@ import java.util.Map; @Component 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 API_KEY; - public static final Gson GSON = new Gson(); public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) { REDMINE_URL = redmineUrl; @@ -34,47 +38,92 @@ public class RedmineClient { } //AQUI OBTENGO LOS TICKETS DESDE REDMINE - public List getTickets(RedmineUser user, boolean includeClosed) { + public List getTickets(RedmineUser user, boolean includeClosed, + int offset, int limit, + LocalDate fechaDesde, + LocalDate fechaHasta) { + List tickets = new ArrayList<>(); HttpClient client = HttpClient.newHttpClient(); - int offset = 0; - // Si includeClose es true, incluira todos los tikets si no, incluira solo los que estan abiertos - String statusFilter = includeClosed ? "&status_id=*" : "&status_id=open"; + // Filtrar tickets abiertos o cerrados + String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed"; + StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() + + statusFilter + "&offset=" + offset + "&limit=" + limit); - while (true) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + statusFilter + "&offset=" + offset)) - .header("Content-Type", "application/json") - .build(); + // 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)); + } - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - String responseBody = response.body(); - List pageTickets = parseTickets(responseBody); - tickets.addAll(pageTickets); - if (pageTickets.size() < PAGE_SIZE) { - break; - } - offset += PAGE_SIZE; - } else { - System.err.println("Error en la respuesta: " + response.statusCode()); - break; - } - } catch (Exception e) { - e.printStackTrace(); - break; + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url.toString())) + .header("Content-Type", "application/json") + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + tickets.addAll(parseTickets(response.body())); + } else { + System.err.println("Error en la respuesta: " + response.statusCode()); } + } catch (Exception e) { + e.printStackTrace(); } - System.out.println("Total tickets obtenidos: " + tickets.size()); + return tickets; } + public int getTotalTickets(RedmineUser user, + boolean includeClosed, + LocalDate fechaDesde, + LocalDate fechaHasta) { + + HttpClient client = HttpClient.newHttpClient(); + + // Filtrar tickets abiertos o cerrados + String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed"; + + StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() + + statusFilter + "&limit=1"); + + // 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)); + } + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url.toString())) + .header("Content-Type", "application/json") + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject(); + return jsonObject.get("total_count").getAsInt(); + } else { + System.err.println("Error en la respuesta: " + response.statusCode()); + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + //AQUI OBTENGO LOS TICKETS DESDE REDMINE - public List getTicketsAuthor(RedmineUser user, boolean includeClosed) { + /*public List getTicketsAuthor(RedmineUser user, boolean includeClosed) { List tickets = new ArrayList<>(); HttpClient client = HttpClient.newHttpClient(); int offset = 0; @@ -110,7 +159,7 @@ public class RedmineClient { } System.out.println("Total tickets obtenidos: " + tickets.size()); return tickets; - } + }*/ // Aquí se parsean todos los tickets que existen y se les da un formato con los campos a mostrarse @@ -123,16 +172,13 @@ public class RedmineClient { for (JsonElement issueElement : issues) { 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; - - // Verifica y obtiene el subject 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() : ""; + 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"; if (issue.has("status") && !issue.get("status").isJsonNull()) { JsonObject statusObject = issue.getAsJsonObject("status"); @@ -141,34 +187,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(); + // 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; + if (issue.has("author") && !issue.get("author").isJsonNull()) { + JsonObject authorObj = issue.getAsJsonObject("author"); + if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) { + autor = new Ticket.User(authorObj.get("name").getAsString()); } } - - //Verifica y obtiene el ID del tipo de ticket + // Tracker ID Integer trackerId = null; if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) { JsonObject trackerObject = issue.getAsJsonObject("tracker"); @@ -177,12 +210,26 @@ public class RedmineClient { } } - - // Agrega el ticket a la lista - tickets.add(new Ticket(id, subject, description, status, - dateCreate != null ? dateCreate.toString() : "", - dateClose != null ? dateClose.toString() : "", - trackerId, "Tipo Desconocido")); + // Crear ticket + Ticket ticketObj = new Ticket( + id, + subject, + description, + status, + dateCreate != null ? dateCreate.toString() : "", + dateClose != null ? dateClose.toString() : "", + dateUpdate != null ? dateUpdate.toString() : "", + autor, + trackerId, + "Tipo Desconocido", + estimatedHrs + + ); + + // Log para verificar JSON de ticket + //System.out.println(GSON.toJson(ticketObj)); + + tickets.add(ticketObj); } } else { System.out.println("La respuesta JSON no contiene la clave 'issues'"); @@ -194,6 +241,7 @@ public class RedmineClient { return tickets; } + public RedmineUser getMyAccount(String username) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(REDMINE_URL + "/my/account.json")) @@ -271,7 +319,7 @@ public class RedmineClient { public RedmineUser getUserByUsername(String username) { HttpClient client = HttpClient.newHttpClient(); 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("X-Redmine-API-Key", API_KEY) .build(); @@ -334,4 +382,55 @@ public class RedmineClient { System.err.println(response.body()); } } + + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(LocalDateTime.class, + (JsonSerializer) (src, typeOfSrc, context) -> + new JsonPrimitive(src.toString())) + .registerTypeAdapter(LocalDateTime.class, + (JsonDeserializer) (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; + } + } + + + public List getAllTickets(RedmineUser user, boolean abiertos, + LocalDate fechaDesde, LocalDate fechaHasta) { + List allTickets = new ArrayList<>(); + int offset = 0; + int limit = 100; // Redmine suele permitir hasta 100 por request (puedes probar 1000) + + List batch; + do { + batch = getTickets(user, abiertos, offset, limit, fechaDesde, fechaHasta); + allTickets.addAll(batch); + offset += limit; + } while (!batch.isEmpty()); + + return allTickets; + } + } diff --git a/src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java b/src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java index 0e9736e..269869d 100644 --- a/src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java +++ b/src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java @@ -21,13 +21,15 @@ public class PlanAnual { private String smt; private String estado; private String situacion; + private String encuesta; // Constructor public PlanAnual(int numero, String nomEquipo, String departamento, boolean monitor, boolean teclado, boolean mouse, boolean regulador, boolean cpu, boolean impresora, boolean miniPrint, boolean laptop, boolean escaner, LocalDate fechaProgramada, - LocalDate fechaMantenimiento, String mesplaneado, String smt, String estado, String situacion) { + LocalDate fechaMantenimiento, String mesplaneado, String smt, String estado, String situacion, + String encuesta) { this.numero = numero; this.nomEquipo = nomEquipo; @@ -47,6 +49,7 @@ public class PlanAnual { this.smt = smt; this.estado = estado; this.situacion = situacion; + this.encuesta = encuesta; } // Getters @@ -142,4 +145,12 @@ public class PlanAnual { public void setSituacion(String situacion) { this.situacion = situacion; } + + public String getEncuesta() { + return encuesta; + } + + public void setEncuesta(String encuesta) { + this.encuesta = encuesta; + } } diff --git a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java index 3fbadfa..725746a 100644 --- a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java +++ b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java @@ -2,43 +2,78 @@ package mx.gob.jumapacelaya.models; import java.sql.Date; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; public class Ticket { private final int id; private final String subject; private final String description; private final String status; - private final LocalDate dateCreate; - private LocalDate dateClose; - private User author; + private final LocalDateTime dateCreate; + private LocalDateTime dateClose; + private final LocalDateTime updateOn; + private User autor; private Integer trackerId; private String type; + private Double estimatedHrs; - - public Ticket(int id, String subject, String description, String status, String dateCreate, String dateClose, Integer trackerId, String type) { + public Ticket(int id, String subject, String description, String status, + String dateCreate, String dateClose, String updateOn, + User autor, Integer trackerId, String type, Double estimatedHrs) { this.id = id; this.subject = subject; this.description = description; this.status = status; - // Manejo de la fecha de creación - if (dateCreate != null && !dateCreate.isEmpty()) { - this.dateCreate = LocalDate.parse(dateCreate); // Solo se parsea si no está vacío - } else { - this.dateCreate = null; // Si está vacío, asignar null - } - // Manejo de la fecha de cierre - if (dateClose != null && !dateClose.isEmpty()) { - this.dateClose = LocalDate.parse(dateClose); // Solo se parsea si no está vacío - } else { - this.dateClose = null; // Si está vacío, asignar null - } - this.author = author; + this.dateCreate = parseDate(dateCreate); + this.dateClose = parseDate(dateClose); + this.updateOn = parseDate(updateOn); + + this.autor = autor; this.trackerId = trackerId; this.type = type; + this.estimatedHrs = estimatedHrs; + } + + + /** + * Método para parsear fechas en formato: + * - yyyy-MM-dd + * - yyyy-MM-dd'T'HH:mm:ssZ + * Siempre convierte a zona horaria local del sistema. + */ + private static LocalDateTime parseDate(String dateStr) { + if (dateStr == null || dateStr.isEmpty()) { + return null; + } + + List formatters = List.of( + DateTimeFormatter.ISO_OFFSET_DATE_TIME, + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") + ); + + for (DateTimeFormatter formatter : formatters) { + try { + return LocalDateTime.parse(dateStr, formatter); + } catch (Exception ignored) {} + } + + try { + LocalDate ld = LocalDate.parse(dateStr); + return ld.atStartOfDay(); + } catch (Exception e) { + System.err.println("Error al parsear fecha: " + dateStr); + return null; + } } + public int getId() { return id; } @@ -56,15 +91,15 @@ public class Ticket { } public User getAuthor() { - return author; + return autor; } public void setAuthor(User author) { - this.author = author; + this.autor = author; } - public Date getDateCreate() { - return java.sql.Date.valueOf(this.dateCreate); + public LocalDateTime getDateCreate() { + return dateCreate; } public Integer getTrackerId() { @@ -75,11 +110,11 @@ public class Ticket { this.trackerId = tipoId; } - public LocalDate getDateClose() { + public LocalDateTime getDateClose() { return dateClose; } - public void setDateClose(LocalDate dateClose) { + public void setDateClose(LocalDateTime dateClose) { this.dateClose = dateClose; } @@ -87,6 +122,26 @@ public class Ticket { this.type = type; } + public LocalDateTime getUpdateOn() { + return updateOn; + } + + public LocalDateTime getDateCreateLocal() { + return this.dateCreate; + } + + public LocalDateTime getDateCloseLocal() { + return this.dateClose; + } + + public Double getEstimatedHrs() { + return estimatedHrs; + } + + public void setEstimatedHrs(Double estimatedHrs) { + this.estimatedHrs = estimatedHrs; + } + public static class User { private String username; @@ -111,16 +166,16 @@ public class Ticket { } switch (trackerId) { - case 1,3,4,7: - return "1-2 dias Max"; - case 2,12: - return "2 hrs Max"; case 5: - return "5 dias aprox."; - case 8,11: - return "2-6 hrs Max"; + return "12 horas"; + case 6,16: + return "2 horas"; + case 7,8,12,13: + return "48 horas"; case 9,10: - return "2 hrs Max"; + return "72 horas"; + case 11,14,15: + return "24 horas"; default: return "N/A"; } diff --git a/src/main/java/mx/gob/jumapacelaya/models/encuestas/Pregunta.java b/src/main/java/mx/gob/jumapacelaya/models/encuestas/Pregunta.java new file mode 100644 index 0000000..7bb8de8 --- /dev/null +++ b/src/main/java/mx/gob/jumapacelaya/models/encuestas/Pregunta.java @@ -0,0 +1,48 @@ +package mx.gob.jumapacelaya.models.encuestas; + +public class Pregunta { + + private int preguntaId; + private int encuestaId; + private String pregunta; + private int orden; + + public Pregunta(int preguntaId, int encuestaId, String pregunta, int orden) { + this.preguntaId = preguntaId; + this.encuestaId = encuestaId; + this.pregunta = pregunta; + this.orden = orden; + } + + public int getPreguntaId() { + return preguntaId; + } + + public void setPreguntaId(int preguntaId) { + this.preguntaId = preguntaId; + } + + public int getEncuestaId() { + return encuestaId; + } + + public void setEncuestaId(int encuestaId) { + this.encuestaId = encuestaId; + } + + public String getPregunta() { + return pregunta; + } + + public void setPregunta(String pregunta) { + this.pregunta = pregunta; + } + + public int getOrden() { + return orden; + } + + public void setOrden(int orden) { + this.orden = orden; + } +} diff --git a/src/main/java/mx/gob/jumapacelaya/models/encuestas/Respuesta.java b/src/main/java/mx/gob/jumapacelaya/models/encuestas/Respuesta.java new file mode 100644 index 0000000..535cb9d --- /dev/null +++ b/src/main/java/mx/gob/jumapacelaya/models/encuestas/Respuesta.java @@ -0,0 +1,66 @@ +package mx.gob.jumapacelaya.models.encuestas; + +import java.sql.Timestamp; + +public class Respuesta { + + private int respuestaId; + private int mantenimientoId; + private int preguntaId; + private Timestamp fechaResp; + private Boolean respuesta; + private int empleadoId; + + public Respuesta(int preguntaId, Boolean respuesta) { + this.preguntaId = preguntaId; + this.respuesta = respuesta; + } + + public int getRespuestaId() { + return respuestaId; + } + + public void setRespuestaId(int respuestaId) { + this.respuestaId = respuestaId; + } + + public int getMantenimientoId() { + return mantenimientoId; + } + + public void setMantenimientoId(int mantenimientoId) { + this.mantenimientoId = mantenimientoId; + } + + public int getPreguntaId() { + return preguntaId; + } + + public void setPreguntaId(int preguntaId) { + this.preguntaId = preguntaId; + } + + public Timestamp getFechaResp() { + return fechaResp; + } + + public void setFechaResp(Timestamp fechaResp) { + this.fechaResp = fechaResp; + } + + public Boolean getRespuesta() { + return respuesta; + } + + public void setRespuesta(Boolean respuesta) { + this.respuesta = respuesta; + } + + public int getEmpleadoId() { + return empleadoId; + } + + public void setEmpleadoId(int empleadoId) { + this.empleadoId = empleadoId; + } +} diff --git a/src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java b/src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java index 5227ef6..538de10 100644 --- a/src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java +++ b/src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java @@ -1,6 +1,8 @@ package mx.gob.jumapacelaya.services; import mx.gob.jumapacelaya.models.*; +import mx.gob.jumapacelaya.models.encuestas.Pregunta; +import mx.gob.jumapacelaya.models.encuestas.Respuesta; import oracle.jdbc.OracleConnection; import oracle.jdbc.OraclePreparedStatement; import oracle.sql.CLOB; @@ -17,6 +19,7 @@ import java.sql.*; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Service public class DatabaseService { @@ -162,17 +165,20 @@ public class DatabaseService { /* ----------------Obtener el Plan Anual de Mantenimiento ---------------- */ public List getPlanAnual() { List planAnualList = new ArrayList<>(); - String query = "SELECT p.plananualid, p.nomEquipo, p.area,\n" + - " p.monitor, p.teclado, p.mouse, p.regulador,\n" + - " p.cpu, p.impresora, p.miniPrint, p.laptop, p.escaner,\n" + - " p.fechaprog, m.fecha AS fechaMantenimiento,\n" + - " me.NOMBRE AS MESPLANEADO, p.tecnicosmt, p.estado,\n" + - " COALESCE(v.SITUACION, 'NO REALIZADO') AS SITUACION\n" + - " FROM PLANANUAL p\n" + - " LEFT JOIN MANTENIMIENTOS m ON p.plananualid = m.plananualid\n" + - " LEFT JOIN MESES me ON p.MESID = me.MESID\n" + - " LEFT JOIN VW_SITUACION_MANTENIMIENTO v ON v.PLANANUALID = p.PLANANUALID\n" + - " ORDER BY p.FECHAPROG ASC"; + String query = """ + SELECT p.plananualid, p.nomEquipo, p.area, + p.monitor, p.teclado, p.mouse, p.regulador, + p.cpu, p.impresora, p.miniPrint, p.laptop, p.escaner, + p.fechaprog, m.fecha AS fechaMantenimiento, + me.NOMBRE AS MESPLANEADO, p.tecnicosmt, p.estado, + COALESCE(v.SITUACION, 'NO REALIZADO') AS SITUACION, + m.ENCUESTA\s + FROM PLANANUAL p + LEFT JOIN MANTENIMIENTOS m ON p.plananualid = m.plananualid + LEFT JOIN MESES me ON p.MESID = me.MESID + LEFT JOIN VW_SITUACION_MANTENIMIENTO v ON v.PLANANUALID = p.PLANANUALID + ORDER BY p.FECHAPROG ASC + """; try (Connection connection = getMysqlConnection(); Statement statement = connection.createStatement(); @@ -197,7 +203,8 @@ public class DatabaseService { resultSet.getString("mesplaneado"), resultSet.getString("tecnicosmt"), resultSet.getString("estado"), - resultSet.getString("SITUACION") + resultSet.getString("SITUACION"), + resultSet.getString("ENCUESTA") == null ? "N/A" : resultSet.getString("ENCUESTA") ); planAnualList.add(planAnual); } @@ -211,17 +218,20 @@ public class DatabaseService { /* ----------------Obtener el Plan Anual de Mantenimiento por ID ---------------- */ public PlanAnual getPlanAnualPorId(int id) { - String query = "SELECT p.plananualid, p.nomEquipo, p.area,\n" + - " p.monitor, p.teclado, p.mouse, p.regulador,\n" + - " p.cpu, p.impresora, p.miniPrint, p.laptop, p.escaner,\n" + - " p.fechaprog, m.fecha AS fechaMantenimiento,\n" + - " me.NOMBRE AS MESPLANEADO, p.tecnicosmt, p.estado,\n" + - " COALESCE(v.SITUACION, 'NO REALIZADO') AS SITUACION\n" + - " FROM PLANANUAL p\n" + - " LEFT JOIN MANTENIMIENTOS m ON p.plananualid = m.plananualid\n" + - " LEFT JOIN MESES me ON p.MESID = me.MESID\n" + - " LEFT JOIN VW_SITUACION_MANTENIMIENTO v ON v.PLANANUALID = p.PLANANUALID\n" + - " where p.plananualid = ?"; + String query = """ + SELECT p.plananualid, p.nomEquipo, p.area, + p.monitor, p.teclado, p.mouse, p.regulador, + p.cpu, p.impresora, p.miniPrint, p.laptop, p.escaner, + p.fechaprog, m.fecha AS fechaMantenimiento, + me.NOMBRE AS MESPLANEADO, p.tecnicosmt, p.estado, + COALESCE(v.SITUACION, 'NO REALIZADO') AS SITUACION, + m.ENCUESTA + FROM PLANANUAL p + LEFT JOIN MANTENIMIENTOS m ON p.plananualid = m.plananualid + LEFT JOIN MESES me ON p.MESID = me.MESID + LEFT JOIN VW_SITUACION_MANTENIMIENTO v ON v.PLANANUALID = p.PLANANUALID + WHERE p.plananualid = ? + """; try (Connection connection = getMysqlConnection(); PreparedStatement statement = connection.prepareStatement(query)) { @@ -246,7 +256,8 @@ public class DatabaseService { resultSet.getString("mesplaneado"), resultSet.getString("tecnicosmt"), resultSet.getString("estado"), - resultSet.getString("SITUACION") + resultSet.getString("SITUACION"), + resultSet.getString("encuesta") ); } } @@ -896,4 +907,137 @@ public class DatabaseService { e.printStackTrace(); } } + + + +/*************************************** ENCUESTAS ******************************************************************************************************/ + public List getPreguntas() { + List preguntas = new ArrayList<>(); + String query = """ + select p.* + from PREGUNTAS p + join ENCUESTAS e on e.ENCUESTAID = p.ENCUESTAID + where e.ENCUESTAID = 1 + order by p.ORDEN ASC + """; + + try (Connection conn = getMysqlConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(query)) { + + while (rs.next()) { + Pregunta pregunta = new Pregunta( + rs.getInt("preguntaid"), + rs.getInt("encuestaid"), + rs.getString("pregunta"), + rs.getInt("orden") + ); + preguntas.add(pregunta); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return preguntas; + } + + + public void insertRespuestas(int manteniminetoId, List respuestas, int empleadoid, String token) { + + String insertQuery = """ + insert into RESPUESTAS + (MANTENIMIENTOID,PREGUNTAID,FECHARESPUESTA,RESPUESTA,EMPLEADOID) + values + (?,?,?,?,?) + """; + + String updateQuery = """ + update MANTENIMIENTOS + set ENCUESTA = 'S' + where MANTENIMIENTOID = ? + """; + + String markTokenUsedQuery = """ + update ENCUESTATOKENS + set USADO = '1' where TOKEN = ? + """; + + try (Connection conn = getMysqlConnection()) { + + conn.setAutoCommit(false); + + try (PreparedStatement insertStmt = conn.prepareStatement(insertQuery); + PreparedStatement updateStmt = conn.prepareStatement(updateQuery); + PreparedStatement tokenStmt = conn.prepareStatement(markTokenUsedQuery)) { + + Timestamp ahora = new Timestamp(System.currentTimeMillis()); + + // 1. Insertar las respuestas en el Batch + for (Respuesta r : respuestas) { + insertStmt.setInt(1, manteniminetoId); + insertStmt.setInt(2, r.getPreguntaId()); + insertStmt.setTimestamp(3, ahora); + insertStmt.setBoolean(4, r.getRespuesta()); + insertStmt.setInt(5, empleadoid); + insertStmt.addBatch(); + } + + insertStmt.executeBatch(); + + // 2. Marcar mantenimiento como encuestado + updateStmt.setInt(1, manteniminetoId); + updateStmt.executeUpdate(); + + // 3. Invalidar el token para que no se use de nuevo + tokenStmt.setString(1, token); + tokenStmt.executeUpdate(); + + conn.commit(); + + } catch (SQLException e) { + conn.rollback(); + throw e; + } + + } catch (Exception e) { + throw new RuntimeException("Error al guardar encuesta y procesar el token", e); + } + } + + + public String crearTokenEncuesta(int mantenimientoid) { + String token = UUID.randomUUID().toString(); + Timestamp expira = new Timestamp(System.currentTimeMillis() + (24 * 60 * 60 * 1000)); + + String query = "INSERT INTO ENCUESTATOKENS (TOKEN, MANTENIMIENTOID, FECHAEXPIRACION) VALUES (?, ?, ?)"; + + try (Connection conn = getMysqlConnection(); + PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, token); + stmt.setInt(2, mantenimientoid); + stmt.setTimestamp(3, expira); + stmt.executeUpdate(); + + } catch (SQLException e) { + e.printStackTrace(); + } + return token; + } + + public Integer validarTokenYObtenerId(String token) { + String query = "SELECT MANTENIMIENTOID FROM ENCUESTATOKENS " + + "WHERE TOKEN = ? AND FECHAEXPIRACION > NOW() AND USADO = FALSE"; + + try (Connection conn = getMysqlConnection(); + PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, token); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return rs.getInt("MANTENIMIENTOID"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + /***************************************************************************************************************************************/ } diff --git a/src/main/java/mx/gob/jumapacelaya/services/EmailService.java b/src/main/java/mx/gob/jumapacelaya/services/EmailService.java index d5c675a..06ad057 100644 --- a/src/main/java/mx/gob/jumapacelaya/services/EmailService.java +++ b/src/main/java/mx/gob/jumapacelaya/services/EmailService.java @@ -36,7 +36,6 @@ public class EmailService { helper.addInline("image_id", imgResource); - mailSender.send(mensaje); System.out.println("Correo enviado con imagen exitosamente"); diff --git a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java index 6404a70..cdf546b 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java @@ -3,10 +3,16 @@ package mx.gob.jumapacelaya.ui; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.checkbox.CheckboxGroup; +import com.vaadin.flow.component.checkbox.CheckboxGroupVariant; +import com.vaadin.flow.component.datepicker.DatePicker; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; 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.H3; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.icon.Icon; @@ -16,10 +22,15 @@ 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.popover.Popover; +import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.component.richtexteditor.RichTextEditor; import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.function.SerializableBiConsumer; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.StreamRegistration; +import com.vaadin.flow.server.StreamResource; import jakarta.annotation.security.PermitAll; import mx.gob.jumapacelaya.api.RedmineClient; import mx.gob.jumapacelaya.api.ServerProperties; @@ -28,13 +39,27 @@ import mx.gob.jumapacelaya.models.RedmineUser; import mx.gob.jumapacelaya.models.Ticket; import mx.gob.jumapacelaya.services.LdapService; import mx.gob.jumapacelaya.services.UserService; - - +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.text.SimpleDateFormat; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.stream.Stream; @PermitAll @@ -43,89 +68,245 @@ import java.util.*; @CssImport("./themes/sistema-mantenimiento/styles.css") public class ActDiariaView extends VerticalLayout { + private static final Logger log = LoggerFactory.getLogger(ActDiariaView.class); private final RedmineClient redmineClient; private final UserService userService; private final Grid grid; - + private final HorizontalLayout showColumnsLyt; + private final Button btnColumns; + private final Button btnExport; + private final HorizontalLayout opcionesLyt; + private Checkbox chkSoloAbiertos; + private DatePicker fechaDesde; + private DatePicker fechaHasta; + private Button btnBuscar; public ActDiariaView(ServerProperties properties, RedmineClient redmineClient, UserService userService) { this.userService = userService; this.redmineClient = redmineClient; this.grid = new Grid<>(Ticket.class, false); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); + + // Layout de opciones + opcionesLyt = new HorizontalLayout(); + opcionesLyt.setWidthFull(); + opcionesLyt.setMargin(false); + opcionesLyt.getStyle() + .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") + .set("border-radius", "10px") + .set("background-color", "white") + .set("padding", "1rem") + .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"); + + // Listener del checkbox + chkSoloAbiertos.addValueChangeListener(e -> { + boolean soloAbiertos = e.getValue(); // marcado = abiertos + loadTickets(soloAbiertos); + + fechaDesde.setEnabled(!soloAbiertos); + fechaHasta.setEnabled(!soloAbiertos); + btnBuscar.setEnabled(!soloAbiertos); + }); + + // Estado inicial + fechaDesde.setEnabled(true); + fechaHasta.setEnabled(true); + btnBuscar.setEnabled(true); + btnBuscar.addClickListener(e -> filtrarTickets()); + + // Cargar tickets iniciales (cerrados) + loadTickets(false); + + opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar); + // Configuración de columnas del grid grid.addColumn(Ticket::getId).setHeader("No.") - .setAutoWidth(true).setFlexGrow(0).setSortable(true); + .setAutoWidth(true) + .setSortable(true); grid.addColumn(Ticket::getType) .setHeader("Tipo") - .setAutoWidth(true); + .setAutoWidth(true) + .setKey("tipo"); - grid.addColumn(Ticket::getSubject).setHeader("Asunto") - .setWidth("25em"); + grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())) + .setHeader("Tiempo estimado") + .setAutoWidth(true) + .setKey("tiempoEst"); - grid.addColumn(createStatusRender()).setHeader("Estado"); + grid.addColumn(ticket -> { + LocalDateTime fecha = ticket.getDateCreate(); + return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; + }).setHeader("Fecha creación") + .setAutoWidth(true) + .setKey("fechaCreacion"); grid.addColumn(ticket -> { - Date date = ticket.getDateCreate(); - if (date != null) { - SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy"); - return formatter.format(date); + LocalDateTime fecha = ticket.getDateClose(); + return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; + }).setHeader("Fecha cierre") + .setAutoWidth(true) + .setKey("fechaCierre"); + + grid.addColumn(ticket -> { + LocalDateTime fecha = ticket.getUpdateOn(); + return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; + }).setHeader("Fecha actualización") + .setAutoWidth(true) + .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("Fecha creacion").setAutoWidth(true); + }).setHeader("Situación").setAutoWidth(true).setKey("cumplimientoHoras"); + + grid.addColumn(createStatusRender()) + .setHeader("Estado") + .setKey("estado"); + grid.addColumn(ticket -> { - LocalDate fechaCierre = ticket.getDateClose(); - if (fechaCierre != null) { - DateTimeFormatter formatterClose = DateTimeFormatter.ofPattern("dd/MM/yyyy"); - return fechaCierre.format(formatterClose); + if (ticket.getDateCreateLocal() != null) { + LocalDateTime fechaInicio = ticket.getDateCreateLocal(); + LocalDateTime fechaFin = (ticket.getDateCloseLocal() != null) + ? ticket.getDateCloseLocal() + : LocalDateTime.now(); + long dias = ChronoUnit.DAYS.between(fechaInicio, fechaFin); + return dias + " días"; } else { return ""; } - }).setHeader("Fecha cierre").setAutoWidth(true); + }).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.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())).setHeader("Tiempo estimado de atencion").setAutoWidth(false); grid.addComponentColumn(ticket -> { Button btnVer = new Button(new Icon(VaadinIcon.EYE)); btnVer.addClickListener(event -> showDescription(ticket)); btnVer.getStyle().set("color", "#A02142"); return btnVer; - }).setAutoWidth(true); - - grid.addColumn(buttonTicketComponentRenderer()).setAutoWidth(true); + }); grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT); - grid.getStyle().set("opacity", "0.8"); - grid.setAllRowsVisible(false); + grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); grid.setSizeFull(); - // Ajustar tamaño del Grid y Layout + // 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(); + columnsSelectorLyt.setAlignItems(Alignment.END); + Popover popover = new Popover(); + popover.setModal(true); + popover.setBackdropVisible(true); + popover.setPosition(PopoverPosition.BOTTOM_END); + popover.setTarget(btnColumns); + Div heading = new Div("Ver/Ocultar columnas"); + heading.getStyle().set("font-weight", "600"); + heading.getStyle().set("padding", "var(--lumo-space-xs)"); + List columns = List.of("tipo","tiempoEst","asunto","autor","estado","fechaCreacion","fechaCierre","fechaActualizacion","duracion"); + CheckboxGroup chkColumns = new CheckboxGroup<>(); + chkColumns.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL); + chkColumns.setItems(columns); + chkColumns.setItemLabelGenerator((item) -> { + String label = StringUtils + .join(StringUtils.splitByCharacterTypeCamelCase(item), " "); + return StringUtils.capitalize(label.toLowerCase()); + }); + chkColumns.addValueChangeListener((e) -> { + columns.forEach((key) -> { + Grid.Column col = grid.getColumnByKey(key); + if (col != null) { + col.setVisible(e.getValue().contains(key)); + } else { + System.out.println("Columna no encontrada para: " + key); + } + }); + }); + Set defaultColumns = Set.of("tipo","tiempoEst","fechaCreacion","fechaCierre","cumplimientoHoras","estado","duracion","autor","asunto"); + chkColumns.setValue(defaultColumns); + popover.add(heading, chkColumns); + + // Ajustar tamaños grid.setSizeFull(); setSizeFull(); + add(opcionesLyt, showColumnsLyt, grid); + setMargin(false); + } - add(grid); - //expand(grid); - setMargin(false); - loadTickets(); + // NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados + private void loadTickets(boolean soloAbiertos) { + RedmineUser user = userService.getRedmineUser(); + + // 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() + ) + ); } - private void loadTickets() { - try { - List tickets = redmineClient.getTickets(userService.getRedmineUser(), true); - List filteredTickets = tickets.stream() - .filter(ticket -> ticket.getTrackerId() == 13 || ticket.getTrackerId() == 14) - .toList(); - grid.setItems(filteredTickets); - } catch (Exception e) { - e.printStackTrace(); - // Manejo de error al cargar los tickets - } - } private ComponentRenderer createStatusRender() { return new ComponentRenderer<>(ticket -> { @@ -134,34 +315,28 @@ public class ActDiariaView extends VerticalLayout { // Estilos basados en el estado del ticket 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; - 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; case "qa": - span.getElement().getStyle().set("color","#C21DF2"); + String theme3 = String.format("badge %s", "warning"); + span.getElement().setAttribute("theme",theme3); break; default: - span.getElement().getStyle().set("color","green"); - break; + String theme4 = String.format("badge %s", ""); + span.getElement().setAttribute("theme",theme4); } return span; }); } - public ComponentRenderer buttonTicketComponentRenderer() { + /*public ComponentRenderer buttonTicketComponentRenderer() { return new ComponentRenderer<>(ticket -> { Button button = new Button(new Icon(VaadinIcon.EDIT) ); button.getStyle().set("color", "#A02142"); @@ -178,7 +353,7 @@ public class ActDiariaView extends VerticalLayout { }); return button; }); - } + }*/ public void cerrarTicket(Ticket ticket, RedmineUser user) { try { @@ -220,4 +395,89 @@ public class ActDiariaView extends VerticalLayout { dialog.open(); } + + + // 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]); + } + + 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 (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"); + } + } + + 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); + } + } + + + private void filtrarTickets() { + boolean soloAbiertos = chkSoloAbiertos.getValue(); + loadTickets(soloAbiertos); + } } diff --git a/src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java b/src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java index a2b404e..a06ec7f 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java @@ -9,10 +9,12 @@ import java.util.List; import java.util.Map; import com.vaadin.flow.component.UI; -import com.vaadin.flow.component.html.Anchor; +import com.vaadin.flow.component.html.*; import com.vaadin.flow.server.StreamRegistration; import com.vaadin.flow.server.StreamResource; +import mx.gob.jumapacelaya.services.EmailService; import mx.gob.jumapacelaya.services.ReportService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.vaadin.lineawesome.LineAwesomeIcon; @@ -22,9 +24,6 @@ import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.gridpro.GridPro; -import com.vaadin.flow.component.html.H3; -import com.vaadin.flow.component.html.Image; -import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.notification.Notification; @@ -54,28 +53,28 @@ import mx.gob.jumapacelaya.services.SecurityService; @CssImport("./themes/sistema-mantenimiento/styles.css") public class DetallesMantView extends VerticalLayout implements BeforeEnterObserver { - private H3 id; - private VerticalLayout mainLayout; - private HorizontalLayout headerLayout; - private HorizontalLayout layout2; - private HorizontalLayout layout3; - private HorizontalLayout botonesLayout; + @Value("${app.base-url}") + private String baseUrl; + + private final H3 id; + private final EmailService emailService; private HorizontalLayout fechasLayout; private TextField txtId; - private TextField txtEquipo; - private ComboBox cbTipo; - private TextField txtFecha; - private TextField txtFechaProgramada; - private TextField txtSituacion; - private ComboBox cbUsuario; - private ComboBox cbDepartamento; - private GridPro gridHardware; - private GridPro gridActualizaciones; - private Button btnEditar; - private Button btnEditarFirmas; - private Button btnImprimirRepo; - private Button btnCancelar; - private Button btnGuardar; + private final TextField txtEquipo; + private final ComboBox cbTipo; + private final TextField txtFecha; + private final TextField txtFechaProgramada; + private final TextField txtSituacion; + private final ComboBox cbUsuario; + private final ComboBox cbDepartamento; + private final GridPro gridHardware; + private final GridPro gridActualizaciones; + private final Button btnEditar; + private final Button btnEditarFirmas; + private final Button btnImprimirRepo = new Button("Imprimir Reporte", new Icon(VaadinIcon.PRINT)); + private final Button btnCancelar; + private final Button btnGuardar; + private final Button btnEnviarEncuesta = new Button("Enviar encuesta", LineAwesomeIcon.ENVELOPE_SOLID.create()); private int planAnualIdActual; private int mantenimientoIdActual; private Dialog confirmDialog; @@ -86,34 +85,31 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser private Image firmaUsuarioImg; private Image firmaSmtImg; private Image firmaGciaImg; + Notification avisoEncuestaNtf; private VerticalLayout userSignLayout; private VerticalLayout smtSignLayout; private VerticalLayout gciaSignLayout; - private final SecurityService securityService; private final DatabaseService service; - private final ReportService reportService; - public DetallesMantView(SecurityService securityService, DatabaseService service, ReportService reportService) { + public DetallesMantView(SecurityService securityService, DatabaseService service, ReportService reportService, EmailService emailService) { this.service = service; - this.securityService = securityService; - this.reportService = reportService; setPadding(true); - mainLayout = new VerticalLayout(); + VerticalLayout mainLayout = new VerticalLayout(); mainLayout.setHeightFull(); mainLayout.getStyle() - .set("box-shadow","0 4px 8px rgba(0,0,0,0.2)") - .set("border-radius", "12px") - .set("background-color", "white") - .set("padding", "1rem") - .set("margin", "1rem auto"); + .set("box-shadow","0 4px 8px rgba(0,0,0,0.2)") + .set("border-radius", "12px") + .set("background-color", "white") + .set("padding", "1rem") + .set("margin", "1rem auto"); id = new H3(); - headerLayout = new HorizontalLayout(); + HorizontalLayout headerLayout = new HorizontalLayout(); headerLayout.setWidthFull(); headerLayout.add(id); @@ -129,7 +125,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser cbDepartamento.setItems(service.getDepartamentos()); cbDepartamento.setItemLabelGenerator(DepartamentosModel::getNombre); - layout2 = new HorizontalLayout(); + HorizontalLayout layout2 = new HorizontalLayout(); layout2.setWidthFull(); txtEquipo = new TextField("Equipo:"); txtEquipo.setReadOnly(true); @@ -147,7 +143,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser txtSituacion.setReadOnly(true); fechasLayout.add(txtFechaProgramada,txtFecha,txtSituacion); - layout3 = new HorizontalLayout(); + HorizontalLayout layout3 = new HorizontalLayout(); layout3.setWidthFull(); cbUsuario.setReadOnly(true); cbDepartamento.setReadOnly(true); @@ -159,22 +155,22 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser // Grid que muestra los detalles del hardware gridHardware = new GridPro<>(); gridHardware.addColumn(item -> item.getDescripcion()) - .setHeader("Descripción"); + .setHeader("Descripción"); gridHardware.addEditColumn(HardwareDetalle::getNumSerie) - .text((item, newValue) -> item.setNumSerie(newValue)) - .setHeader("No. Serie") - .setEditorComponent(new TextField()); + .text((item, newValue) -> item.setNumSerie(newValue)) + .setHeader("No. Serie") + .setEditorComponent(new TextField()); gridHardware.addEditColumn(HardwareDetalle::getModelo) - .text((item, newValue) -> item.setModelo(newValue)) - .setHeader("Modelo") - .setEditorComponent(new TextField()); + .text((item, newValue) -> item.setModelo(newValue)) + .setHeader("Modelo") + .setEditorComponent(new TextField()); gridHardware.addEditColumn(HardwareDetalle::getPlaca) - .text((item, newValue) -> item.setPlaca(newValue)) - .setHeader("Placa") - .setEditorComponent(new TextField()); + .text((item, newValue) -> item.setPlaca(newValue)) + .setHeader("Placa") + .setEditorComponent(new TextField()); gridHardware.setWidthFull(); gridHardware.setEditOnClick(false); @@ -186,14 +182,14 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser // Grid que muestra las actualizaciones de seguridad gridActualizaciones = new GridPro<>(); gridActualizaciones.addEditColumn(ActualizacioneSeguridadModel::getDescripcion) - .text((item, newValue) -> item.setDescripcion(newValue)) - .setHeader("Descripción") - .setEditorComponent(new TextField()); - + .text((item, newValue) -> item.setDescripcion(newValue)) + .setHeader("Descripción") + .setEditorComponent(new TextField()); + gridActualizaciones.addEditColumn(ActualizacioneSeguridadModel::getOtrasactualizaciones) - .text((item, newValue) -> item.setOtrasactualizaciones(newValue)) - .setHeader("Otras Actualizaciones") - .setEditorComponent(new TextField()); + .text((item, newValue) -> item.setOtrasactualizaciones(newValue)) + .setHeader("Otras Actualizaciones") + .setEditorComponent(new TextField()); gridActualizaciones.setWidthFull(); gridActualizaciones.setEditOnClick(false); @@ -201,16 +197,18 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser gridActualizaciones.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); + HorizontalLayout botonesHeaderLyt = new HorizontalLayout(); + botonesHeaderLyt.setWidthFull(); + botonesHeaderLyt.add(btnImprimirRepo, btnEnviarEncuesta); - botonesLayout = new HorizontalLayout(); + HorizontalLayout botonesLayout = new HorizontalLayout(); botonesLayout.setWidthFull(); botonesLayout.setJustifyContentMode(JustifyContentMode.CENTER); btnEditar = new Button("Editar", new Icon(VaadinIcon.EDIT)); - btnImprimirRepo = new Button("Imprimir Reporte", new Icon(VaadinIcon.PRINT)); btnEditarFirmas = new Button("Editar firmas", LineAwesomeIcon.SIGNATURE_SOLID.create()); btnGuardar = new Button("Guardar", LineAwesomeIcon.SAVE_SOLID.create()); btnCancelar = new Button("Cancelar", new Icon(VaadinIcon.CLOSE_CIRCLE_O)); - botonesLayout.add(btnEditar,btnEditarFirmas,btnImprimirRepo,btnGuardar,btnCancelar); + botonesLayout.add(btnEditar,btnEditarFirmas,btnGuardar,btnCancelar); btnGuardar.setVisible(false); btnGuardar.getStyle().set("background-color", "#008000"); @@ -304,6 +302,12 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser }); + btnEnviarEncuesta.addClickListener(e -> { + avisoEncuestaNtf.close(); + enviarCorreo(); + }); + + // Se dispara el dialogo de confirmacion btnGuardar.addClickListener(e -> confirmDialog.open()); @@ -317,10 +321,10 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser txtJustificacion.setRequired(true); confirmDialog.add( - new VerticalLayout( - new Span("¿Estás seguro de que deseas actualizar el mantenimiento?"), - txtJustificacion - ) + new VerticalLayout( + new Span("¿Estás seguro de que deseas actualizar el mantenimiento?"), + txtJustificacion + ) ); Button btnConfirmar = new Button("Actualizar", event -> { @@ -371,111 +375,113 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser addSignatureSection(); - mainLayout.add(headerLayout,layout2,fechasLayout,layout3,gridHardware,gridActualizaciones,firmasLAyout,botonesLayout); - add(mainLayout); + mainLayout.add(headerLayout, layout2,fechasLayout, layout3,gridHardware,gridActualizaciones,firmasLAyout, botonesLayout); + add(botonesHeaderLyt,mainLayout); + this.setSpacing(false); + this.emailService = emailService; } private void realizarActualizacion() { boolean exito = true; - // Obtén los objetos seleccionados - TiposMantenimiento tipoSeleccionado = cbTipo.getValue(); - Usuario usuarioSeleccionado = cbUsuario.getValue(); - DepartamentosModel departamentoSeleccionado = cbDepartamento.getValue(); + // Obtén los objetos seleccionados + TiposMantenimiento tipoSeleccionado = cbTipo.getValue(); + Usuario usuarioSeleccionado = cbUsuario.getValue(); + DepartamentosModel departamentoSeleccionado = cbDepartamento.getValue(); - int tipoId = tipoSeleccionado != null ? Integer.parseInt(tipoSeleccionado.getTipomantId()) : 0; - int empleadoId = usuarioSeleccionado != null ? Integer.parseInt(usuarioSeleccionado.getEmpleadoId()) : 0; - String departamentoId = departamentoSeleccionado != null ? departamentoSeleccionado.getDepartamentoId() : null; + int tipoId = tipoSeleccionado != null ? Integer.parseInt(tipoSeleccionado.getTipomantId()) : 0; + int empleadoId = usuarioSeleccionado != null ? Integer.parseInt(usuarioSeleccionado.getEmpleadoId()) : 0; + String departamentoId = departamentoSeleccionado != null ? departamentoSeleccionado.getDepartamentoId() : null; - // Fechas (corrige el campo) - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); - LocalDate fechaProg = !txtFechaProgramada.getValue().isEmpty() ? LocalDate.parse(txtFechaProgramada.getValue(), formatter) : null; - LocalDate fechaRealizado = !txtFecha.getValue().isEmpty() ? LocalDate.parse(txtFecha.getValue(), formatter) : null; + // Fechas (corrige el campo) + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate fechaProg = !txtFechaProgramada.getValue().isEmpty() ? LocalDate.parse(txtFechaProgramada.getValue(), formatter) : null; + LocalDate fechaRealizado = !txtFecha.getValue().isEmpty() ? LocalDate.parse(txtFecha.getValue(), formatter) : null; - // Obtener las firmas como en MantenimientoView - byte[] userSignatureBytes = userSignPad.getImageBase64(); - byte[] smtSignatureBytes = smtSignPad.getImageBase64(); + // Obtener las firmas como en MantenimientoView + byte[] userSignatureBytes = userSignPad.getImageBase64(); + byte[] smtSignatureBytes = smtSignPad.getImageBase64(); - String userSignatureBase64 = (userSignatureBytes != null) ? Base64.getEncoder().encodeToString(userSignatureBytes) : null; - String smtSignatureBase64 = (smtSignatureBytes != null) ? Base64.getEncoder().encodeToString(smtSignatureBytes) : null; + String userSignatureBase64 = (userSignatureBytes != null) ? Base64.getEncoder().encodeToString(userSignatureBytes) : null; + String smtSignatureBase64 = (smtSignatureBytes != null) ? Base64.getEncoder().encodeToString(smtSignatureBytes) : null; - // Si la firma está vacía, conserva la anterior - if (userSignatureBase64 == null || esFirmaVacia(userSignatureBase64)) { - userSignatureBase64 = service.getDetalleMantenimientoPorPlanAnualId(planAnualIdActual).getFirmaUser(); - } - if (smtSignatureBase64 == null || esFirmaVacia(smtSignatureBase64)) { - smtSignatureBase64 = service.getDetalleMantenimientoPorPlanAnualId(planAnualIdActual).getFirmaSmt(); - } + // Si la firma está vacía, conserva la anterior + if (userSignatureBase64 == null || esFirmaVacia(userSignatureBase64)) { + userSignatureBase64 = service.getDetalleMantenimientoPorPlanAnualId(planAnualIdActual).getFirmaUser(); + } + if (smtSignatureBase64 == null || esFirmaVacia(smtSignatureBase64)) { + smtSignatureBase64 = service.getDetalleMantenimientoPorPlanAnualId(planAnualIdActual).getFirmaSmt(); + } - // ACTUALIZA PLANANUAL (nombre del equipo y fecha programada) - if (!service.actualizarPlanAnual(planAnualIdActual, txtEquipo.getValue())) { - exito = false; - } - // ACTUALIZA MANTENIMIENTOS (tipo, departamento, usuario, fecha realizado) - if (!service.actualizarMantenimiento( + // ACTUALIZA PLANANUAL (nombre del equipo y fecha programada) + if (!service.actualizarPlanAnual(planAnualIdActual, txtEquipo.getValue())) { + exito = false; + } + // ACTUALIZA MANTENIMIENTOS (tipo, departamento, usuario, fecha realizado) + if (!service.actualizarMantenimiento( mantenimientoIdActual, tipoId, departamentoId, empleadoId, fechaRealizado, txtEquipo.getValue(), - userSignatureBase64, - smtSignatureBase64 - )) { - exito = false; - } + userSignatureBase64, + smtSignatureBase64 + )) { + exito = false; + } - // ACTUALIZA HARDWARE (grid) - for (HardwareDetalle detalle : gridHardware.getListDataView().getItems().toList()) { - if (!service.actualizarHardwareDetalle(detalle)) { - exito = false; - } + // ACTUALIZA HARDWARE (grid) + for (HardwareDetalle detalle : gridHardware.getListDataView().getItems().toList()) { + if (!service.actualizarHardwareDetalle(detalle)) { + exito = false; } + } - // ACTUALIZA ACTUALIZACIONES DE SEGURIDAD (grid) - for (ActualizacioneSeguridadModel actualizacion : gridActualizaciones.getListDataView().getItems().toList()) { - if (!service.actualizarActualizacionSeg(actualizacion)) { - exito = false; - } + // ACTUALIZA ACTUALIZACIONES DE SEGURIDAD (grid) + for (ActualizacioneSeguridadModel actualizacion : gridActualizaciones.getListDataView().getItems().toList()) { + if (!service.actualizarActualizacionSeg(actualizacion)) { + exito = false; } + } - if (exito) { - Notification.show("Registros actualizados correctamente", 3000, Notification.Position.MIDDLE) + if (exito) { + Notification.show("Registros actualizados correctamente", 3000, Notification.Position.MIDDLE) .addThemeVariants(NotificationVariant.LUMO_SUCCESS); - } else { - Notification.show("Error al actualizar uno o más registros", 3000, Notification.Position.MIDDLE) + } else { + Notification.show("Error al actualizar uno o más registros", 3000, Notification.Position.MIDDLE) .addThemeVariants(NotificationVariant.LUMO_ERROR); - } - // Opcional: volver a modo solo lectura - txtEquipo.setReadOnly(true); - cbTipo.setReadOnly(true); - txtFecha.setReadOnly(true); - cbUsuario.setReadOnly(true); - cbDepartamento.setReadOnly(true); - btnImprimirRepo.setVisible(true); - btnEditar.setVisible(true); - btnEditarFirmas.setVisible(true); - btnGuardar.setVisible(false); - btnCancelar.setVisible(false); - gridHardware.setEditOnClick(false); - gridHardware.getEditor().cancel(); - if (firmaUsuarioImg != null && userSignPad.getParent().isPresent()) { - userSignLayout.replace(userSignPad, firmaUsuarioImg); - } - if (firmaSmtImg != null && smtSignPad.getParent().isPresent()) { - smtSignLayout.replace(smtSignPad, firmaSmtImg); - } + } + // Opcional: volver a modo solo lectura + txtEquipo.setReadOnly(true); + cbTipo.setReadOnly(true); + txtFecha.setReadOnly(true); + cbUsuario.setReadOnly(true); + cbDepartamento.setReadOnly(true); + btnImprimirRepo.setVisible(true); + btnEditar.setVisible(true); + btnEditarFirmas.setVisible(true); + btnGuardar.setVisible(false); + btnCancelar.setVisible(false); + gridHardware.setEditOnClick(false); + gridHardware.getEditor().cancel(); + if (firmaUsuarioImg != null && userSignPad.getParent().isPresent()) { + userSignLayout.replace(userSignPad, firmaUsuarioImg); + } + if (firmaSmtImg != null && smtSignPad.getParent().isPresent()) { + smtSignLayout.replace(smtSignPad, firmaSmtImg); + } } // Metodo para verificar si la firma corresponde a una cadena de firma vacia private boolean esFirmaVacia(String firmaBase64) { - String firmaVacia = "iVBORw0KGgoAAAANSUhEUgAAAXkAAAD8CAYAAACSCdTiAAAIbElEQVR4Xu3UAQ0AIAwDQeZfNCPI+Nwc9Lp07rvjCBAgQCApMEY+2atQBAgQ+AJG3iMQIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBAw8n6AAAECYQEjHy5XNAIECBh5P0CAAIGwgJEPlysaAQIEjLwfIECAQFjAyIfLFY0AAQJG3g8QIEAgLGDkw+WKRoAAASPvBwgQIBAWMPLhckUjQICAkfcDBAgQCAsY+XC5ohEgQMDI+wECBAiEBYx8uFzRCBAgYOT9AAECBMICRj5crmgECBBYAuXtOkIWm1QAAAAASUVORK5CYII="; + String firmaVacia = "iVBORw0KGgoAAAANSUhEUgAAAdQAAAD8CAYAAADOr1WDAAAKOElEQVR4Xu3VsQ0AIAwEMbL/0IBYgStNTwrrpZt93/IIECBAgACBL4ER1C8/nwkQIECAwBMQVEMgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgAABQbUBAgQIECAQCAhqgOgEAQIECBAQVBsgQIAAAQKBgKAGiE4QIECAAAFBtQECBAgQIBAICGqA6AQBAgQIEBBUGyBAgAABAoGAoAaIThAgQIAAAUG1AQIECBAgEAgIaoDoBAECBAgQEFQbIECAAAECgYCgBohOECBAgACBA8Bc7Tp3N5/2AAAAAElFTkSuQmCC"; return firmaBase64.equals(firmaVacia); } @@ -515,18 +521,75 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser } + // METODO PARA ENVIAR LOS CORREOS ELECTRONICOS + private void enviarCorreo() { + Usuario usuarioDestino = cbUsuario.getValue(); + if (usuarioDestino != null && usuarioDestino.getEmail() != null) { + String destinatario = usuarioDestino.getEmail(); + String asunto = "Encuesta de satisfacción -Mantenimiento #" + mantenimientoIdActual; + + String token = service.crearTokenEncuesta(mantenimientoIdActual); + String linkEncuesta = baseUrl + "/encuesta?token=" + token; + + String cuerpo = "" + + "" + + "" + + ""+ + "" + + "" + + ""; + + String imagePath = "META-INF/resources/images/imgCorreo/imgEncuesta.png"; + + emailService.enviarCorreo(destinatario,asunto,cuerpo,imagePath); + Notification.show("Encuesta enviada correctamente para el mantenimiento No. " + mantenimientoIdActual, 3000, Notification.Position.MIDDLE) + .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } else { + Notification.show("Por favor, seleccione un usuario destino", 3000, Notification.Position.MIDDLE); + } + } + + @Override public void beforeEnter(BeforeEnterEvent event) { String idParam = event.getLocation().getQueryParameters().getParameters().get("id") != null ? event.getLocation().getQueryParameters().getParameters().get("id").stream().findFirst().orElse(null) : null; + String encParam = event.getLocation().getQueryParameters().getParameters().getOrDefault("enc", List.of()) + .stream().findFirst().orElse("N").toUpperCase(); + if (idParam != null) { try { int planId = Integer.parseInt(idParam); DetalleMantenimientoModel detalle = service.getDetalleMantenimientoPorPlanAnualId(planId); + if (encParam.equalsIgnoreCase("N")) { + Icon icono = new Icon(VaadinIcon.WARNING); + icono.setSize("40px"); + NativeLabel aviso = new NativeLabel("Aviso:"); + aviso.getStyle().set("font-weight", "bold"); + NativeLabel label = new NativeLabel("¡No se ha respondido una encuesta para este mantenimiento!"); + + VerticalLayout textLyt = new VerticalLayout(aviso,label); + textLyt.setPadding(false); + textLyt.setSpacing(false); + HorizontalLayout layout = new HorizontalLayout(icono, textLyt); + layout.setJustifyContentMode(JustifyContentMode.CENTER); + layout.setAlignItems(Alignment.CENTER); + + avisoEncuestaNtf = new Notification(layout); + avisoEncuestaNtf.setPosition(Notification.Position.TOP_CENTER); + avisoEncuestaNtf.addThemeVariants(NotificationVariant.LUMO_WARNING); + avisoEncuestaNtf.setDuration(5000); + avisoEncuestaNtf.open(); + + } else { + btnEnviarEncuesta.setEnabled(false); + btnEnviarEncuesta.getStyle().set("opacity", "0.5"); + } + if (detalle != null) { this.planAnualIdActual = detalle.getPlanAnualId(); this.mantenimientoIdActual = detalle.getId(); @@ -534,9 +597,9 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser id.setText("Plan ID: " + idParam); txtEquipo.setValue(String.valueOf(detalle.getNombreEquipo())); cbTipo.setValue( - cbTipo.getListDataView().getItems() - .filter(t -> t.getNombre().equals(detalle.getTipo())) - .findFirst().orElse(null) + cbTipo.getListDataView().getItems() + .filter(t -> t.getNombre().equals(detalle.getTipo())) + .findFirst().orElse(null) ); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); @@ -548,14 +611,14 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser txtFecha.setValue(fechaReal); txtSituacion.setValue(situacion); cbUsuario.setValue( - cbUsuario.getListDataView().getItems() - .filter(u -> u.getNombre().equals(detalle.getUsuario())) - .findFirst().orElse(null) + cbUsuario.getListDataView().getItems() + .filter(u -> u.getNombre().equals(detalle.getUsuario())) + .findFirst().orElse(null) ); cbDepartamento.setValue( - cbDepartamento.getListDataView().getItems() - .filter(d -> d.getNombre().equals(detalle.getDepartamento())) - .findFirst().orElse(null) + cbDepartamento.getListDataView().getItems() + .filter(d -> d.getNombre().equals(detalle.getDepartamento())) + .findFirst().orElse(null) ); int mantId = detalle.getId(); List listaHardware = service.getHardwaredetallePorMantId(mantId); @@ -589,7 +652,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser firmaSmtImg.setWidthFull(); smtSignLayout.replace(smtSignPad, firmaSmtImg); } - + Image firmaGcia = new Image("images/FirmaGerenteTI.png", "Firma de la Gcia. de T.I"); firmaGcia.setHeight("200px"); firmaGcia.setWidthFull(); diff --git a/src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java b/src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java new file mode 100644 index 0000000..5ab657b --- /dev/null +++ b/src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java @@ -0,0 +1,216 @@ +package mx.gob.jumapacelaya.ui; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.html.*; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.radiobutton.RadioButtonGroup; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import mx.gob.jumapacelaya.models.encuestas.Pregunta; +import mx.gob.jumapacelaya.models.encuestas.Respuesta; +import mx.gob.jumapacelaya.services.DatabaseService; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Route("encuesta") +@PageTitle("Encuesta de satisfacción") +@AnonymousAllowed +public class EncuestaView extends VerticalLayout implements BeforeEnterObserver { + + private final DatabaseService encuestasDBService; + private int mantenimientoId = -1; + private final VerticalLayout mainLyt = new VerticalLayout(); + private final Span pregunta1Txt = new Span(); + private final RadioButtonGroup pregunta1Rb = new RadioButtonGroup<>(); + private final Button btnEnviar = new Button("Enviar"); + private final TextField txtNumEmpl = new TextField("No. Empleado:"); + private String token; + + + private static class RespuestaComponente { + Pregunta pregunta; + RadioButtonGroup radios; + + RespuestaComponente(Pregunta pregunta, RadioButtonGroup radios) { + this.pregunta = pregunta; + this.radios = radios; + } + } + + private final List respuestasUI = new ArrayList<>(); + + public EncuestaView(DatabaseService encuestasDBService) { + this.encuestasDBService = encuestasDBService; + + setSpacing(true); + setPadding(true); + setSizeFull(); + + this.getStyle() + .set("background-image", "url('/images/LOGO_1024X768.jpg')") + .set("background-repeat", "no-repeat") + .set("background-size", "cover") + .set("background-position", "center"); + + + mainLyt.setHeightFull(); + mainLyt.setWidth("55%"); + mainLyt.getStyle() + .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") + .set("border-radius", "12px") + .set("background-color", "white") + .set("padding", "1rem") + .set("margin", "1rem auto"); + + btnEnviar.addClickListener(e -> procesarRespuestas()); + + this.add(mainLyt); + } + + private void mostrarMensaje() { + + txtNumEmpl.setRequired(true); + txtNumEmpl.setErrorMessage("Por favor, llena este campo"); + + VerticalLayout titulos = new VerticalLayout(); + titulos.setSpacing(false); + titulos.setPadding(false); + titulos.add( + new H2("Mantenimiento Preventivo a Equipo de Computo"), + new Paragraph("La siguiente encuesta tiene como objetivo valorar el servicio que se brinda al proporcionar el mantenimiento preventivo del equipo de computo en el Organismo."), + txtNumEmpl + ); + + VerticalLayout preguntasLyt = new VerticalLayout(); + preguntasLyt.setWidthFull(); + preguntasLyt.setPadding(false); + preguntasLyt.setSpacing(false); + + List preguntas = encuestasDBService.getPreguntas(); + + for (Pregunta p : preguntas) { + HorizontalLayout fila = new HorizontalLayout(); + fila.setWidthFull(); + fila.setAlignItems(Alignment.CENTER); + fila.getStyle().set("padding", "0.4rem"); + fila.getStyle().set("border-bottom", "1px solid #e0e0e0"); + + Span texto = new Span(p.getPregunta()); + texto.getStyle().set("font-weight", "600"); + + RadioButtonGroup radios = new RadioButtonGroup<>(); + radios.setItems("Si", "No"); + + fila.add(texto,radios); + preguntasLyt.add(fila); + + respuestasUI.add(new RespuestaComponente(p,radios)); + } + + HorizontalLayout gracias = new HorizontalLayout(new H3("¡Gracias!")); + gracias.setWidthFull(); + gracias.setJustifyContentMode(JustifyContentMode.CENTER); + + btnEnviar.setWidthFull(); + btnEnviar.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + HorizontalLayout btnEnviarLyt = new HorizontalLayout(btnEnviar); + btnEnviarLyt.setWidthFull(); + btnEnviarLyt.setJustifyContentMode(JustifyContentMode.CENTER); + btnEnviarLyt.setSpacing(false); + btnEnviarLyt.setPadding(false); + + + mainLyt.removeAll(); + mainLyt.add(titulos,preguntasLyt,gracias, btnEnviarLyt); + } + + private void procesarRespuestas() { + + if (txtNumEmpl.isEmpty()) { + txtNumEmpl.setInvalid(true); + return; + } + + int empleadoId = Integer.parseInt(txtNumEmpl.getValue()); + Timestamp ahora = new Timestamp(System.currentTimeMillis()); + List respuestas = new ArrayList<>(); + + for (RespuestaComponente rc : respuestasUI) { + if (rc.radios.getValue() == null) continue; + + respuestas.add(new Respuesta( + rc.pregunta.getPreguntaId(), + rc.radios.getValue().equals("Si") + )); + } + + encuestasDBService.insertRespuestas( + mantenimientoId, + respuestas, + empleadoId, + this.token + ); + + NotiEncuesta(); + mainLyt.setVisible(false); + + this.setEnabled(false); + } + + private void NotiEncuesta() { + Notification ntf = new Notification(); + ntf.setPosition(Notification.Position.MIDDLE); + ntf.addThemeVariants(NotificationVariant.LUMO_SUCCESS); + ntf.setDuration(0); + + H2 titulo = new H2("Encuesta enviada"); + titulo.getStyle().set("color","white"); + Span msj = new Span("Sus respuestas se han guardado correctamente."); + Span msj2 = new Span("Gracias por participar en nuestra encuesta."); + + Icon succesIcon = new Icon(VaadinIcon.CHECK_CIRCLE); + succesIcon.setSize("48px"); + + VerticalLayout layout = new VerticalLayout(succesIcon,titulo,msj,msj2); + layout.setAlignItems(Alignment.CENTER); + + ntf.add(layout); + ntf.open(); + } + + + @Override + public void beforeEnter(BeforeEnterEvent beforeEnterEvent) { + String token = beforeEnterEvent.getLocation().getQueryParameters() + .getParameters().getOrDefault("token", List.of("")).get(0); + + if (!token.isEmpty()) { + Integer idRecuperado = encuestasDBService.validarTokenYObtenerId(token); + + if (idRecuperado != null) { + this.mantenimientoId = idRecuperado; + this.token = token; + mostrarMensaje(); + + } else { + mainLyt.add(new H2("El enlace ha expírado o ya fue utilizado.")); + mainLyt.setAlignItems(Alignment.CENTER); + } + } else { + mainLyt.add(new H2("Acceso no autorizado.")); + mainLyt.setAlignItems(Alignment.CENTER); + } + } +} diff --git a/src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java b/src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java index 50cffb1..32c49a6 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java @@ -565,7 +565,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse "" + ""; - String imagePath = "META-INF/resources/images/imgCorreo/correoMantt.png"; + String imagePath = "META-INF/resources/images/imgCorreo/MttoRealizado.png"; emailService.enviarCorreo(destinatario, asunto, cuerpo, imagePath); diff --git a/src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java b/src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java index b27b695..752039d 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java @@ -33,6 +33,7 @@ import com.vaadin.flow.component.textfield.NumberField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MemoryBuffer; +import com.vaadin.flow.data.provider.ListDataProvider; import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.router.PageTitle; @@ -60,6 +61,8 @@ import java.time.Year; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; @PermitAll @PageTitle("Plan Anual de Mantenimiento") @@ -82,8 +85,10 @@ public class PlanAnualView extends VerticalLayout { Button btnColumns; Button btnImprimirRpt; Button btnAddEquipo; + Button btnEnviarEncuestas; HorizontalLayout btnImprimirLayout; private Popover reportePopover; + ComboBox yearFilter; public PlanAnualView(DatabaseService databaseService, Environment env, ReportService reportService) { this.databaseService = databaseService; @@ -104,11 +109,14 @@ public class PlanAnualView extends VerticalLayout { GridListDataView dataView = planAnualGrid.setItems(planAnualItems); - // Se crea el filtro para el grid. PlanAnualFilter planAnualFilter = new PlanAnualFilter(dataView); planAnualFilter.setExcludeRealizado(true); + yearFilter.addValueChangeListener(event -> + planAnualFilter.setYear(event.getValue()) + ); + HeaderRow headerRow = planAnualGrid.appendHeaderRow(); /*headerRow.getCell(planAnualGrid.getColumnByKey("smtColumnKey")) .setComponent(createFilterHeader("S.M.T", planAnualFilter::setSmt));*/ @@ -218,7 +226,8 @@ public class PlanAnualView extends VerticalLayout { this.setMargin(false); this.setSpacing(false); this.setSizeFull(); - add(filtrosLayout, gridLayout, uploadLayout); + add(filtrosLayout, uploadLayout); + add(gridLayout); } private void setupHeader() { @@ -262,13 +271,16 @@ public class PlanAnualView extends VerticalLayout { LocalDate fechaSistema = LocalDate.now(); String nomEquipo = planAnual.getNomEquipo(); String departamento = planAnual.getDepartamento(); + String encuesta = planAnual.getEncuesta(); btn.getUI().ifPresent(ui -> ui.navigate( "mantenimiento?id=" + idPlananual + "&fecha=" + fechaSistema + "&tipo=1" + "&nomEquipo=" + nomEquipo + - "&departamento=" + departamento)); + "&departamento=" + departamento + + "&enc=" + encuesta + )); }); } else if ("REALIZADO".equalsIgnoreCase(estado)) { @@ -278,8 +290,12 @@ public class PlanAnualView extends VerticalLayout { btn.addClickListener(event -> { int idPlananual = planAnual.getNumero(); + String encuesta = planAnual.getEncuesta(); + btn.getUI().ifPresent(ui -> ui.navigate( - "detalles?id=" + idPlananual)); + "detalles?id=" + idPlananual + + "&enc=" + encuesta + )); }); } else { btn = new Button("N/A"); @@ -318,6 +334,9 @@ public class PlanAnualView extends VerticalLayout { planAnualGrid.addColumn((ValueProvider) PlanAnual::getSituacion) .setHeader("Situación").setAutoWidth(true).setKey("situacion"); + planAnualGrid.addColumn((ValueProvider) PlanAnual::getEncuesta) + .setHeader("Encuesta").setAutoWidth(true).setKey("encuesta"); + /*planAnualGrid.addColumn((ValueProvider) PlanAnual::getSmt) .setHeader("S.M.T").setKey("smtColumnKey");*/ @@ -349,8 +368,29 @@ public class PlanAnualView extends VerticalLayout { btnAddEquipo.addClickListener(event -> addNuevoEquipo()); btnAddEquipo.setTooltipText("Agregar nuevo equipo"); - btnImprimirLayout = new HorizontalLayout(btnColumns, btnImprimirRpt, btnAddEquipo); - + btnEnviarEncuestas = new Button(VaadinIcon.ENVELOPE.create()); + ConfirmDialog enviarEncConfirm = new ConfirmDialog(); + enviarEncConfirm.setHeader("Enviar encuestas"); + enviarEncConfirm.setText("¿Deseas enviar las encuestas de satisfacción?, Esto enviara la encuesta solo a los mantenimientos realizados"); + enviarEncConfirm.setCancelable(true); + enviarEncConfirm.addCancelListener(e -> enviarEncConfirm.close()); + enviarEncConfirm.setConfirmText("Enviar"); + enviarEncConfirm.addConfirmListener(e -> {}); + btnEnviarEncuestas.addClickListener(e -> enviarEncConfirm.open()); + + yearFilter = new ComboBox<>(); + int currentYear = Year.now().getValue(); + List years = IntStream.rangeClosed(currentYear - 1, currentYear + 4) + .boxed().collect(Collectors.toList()); + yearFilter.setItems(years); + yearFilter.setPlaceholder("Año"); + yearFilter.setClearButtonVisible(true); + + List todosLosPlanes = databaseService.getPlanAnual(); + + + btnImprimirLayout = new HorizontalLayout(btnColumns, btnImprimirRpt, btnAddEquipo/*, btnEnviarEncuestas*/, yearFilter); + btnImprimirLayout.setAlignItems(Alignment.BASELINE); HorizontalLayout columnSelectorLayout = new HorizontalLayout(); columnSelectorLayout.setAlignItems(Alignment.END); @@ -388,12 +428,11 @@ public class PlanAnualView extends VerticalLayout { }); Set defaultColumns = Set.of("equipo", "departamento", "mesplaneado", - "fechaProgramada", "fechaMantenimiento", "estado", "situacion"); + "fechaProgramada", "fechaMantenimiento", "estado", "situacion", "encuesta"); chkColumns.setValue(defaultColumns); popover.add(heading, chkColumns); - // Cargar datos - planAnualGrid.setItems(databaseService.getPlanAnual()); + return planAnualGrid; } @@ -495,11 +534,14 @@ public class PlanAnualView extends VerticalLayout { } private static class PlanAnualFilter { + private final GridListDataView dataView; + private String smt; private String equipo; private String departamento; private String mesPlaneado; + private Integer year; private boolean excludeRealizado = true; public PlanAnualFilter(GridListDataView dataView) { @@ -527,35 +569,51 @@ public class PlanAnualView extends VerticalLayout { this.dataView.refreshAll(); } + public void setYear(Integer year) { + this.year = year; + dataView.refreshAll(); + } + public void setExcludeRealizado(boolean excludeRealizado) { this.excludeRealizado = excludeRealizado; this.dataView.refreshAll(); } public boolean test(PlanAnual planAnual) { - if (planAnual == null) { - return false; // Avoid NullPointerException - } + if (planAnual == null) return false; boolean matchesSmt = matches(planAnual.getSmt(), smt); boolean matchesEquipo = matches(planAnual.getNomEquipo(), equipo); boolean matchesDepartamento = matches(planAnual.getDepartamento(), departamento); boolean matchesMesPlaneado = matches(planAnual.getMesplaneado(), mesPlaneado); + boolean matchesYear = true; + if (year != null) { + LocalDate fecha = planAnual.getFechaProgramada(); + matchesYear = fecha != null && fecha.getYear() == year; + } + + boolean matchesEstado; if (excludeRealizado) { - // Mostrar solo los pendientes - return matchesSmt && matchesEquipo && matchesDepartamento && matchesMesPlaneado - && (planAnual.getEstado() == null || !"REALIZADO".equalsIgnoreCase(planAnual.getEstado())); + matchesEstado = planAnual.getEstado() == null + || !"REALIZADO".equalsIgnoreCase(planAnual.getEstado()); } else { - // Mostrar solo realizados - return matchesSmt && matchesEquipo && matchesDepartamento && matchesMesPlaneado - && "REALIZADO".equalsIgnoreCase(planAnual.getEstado()); + matchesEstado = + "REALIZADO".equalsIgnoreCase(planAnual.getEstado()) && + !"NO REALIZADO".equalsIgnoreCase(planAnual.getSituacion()); } + + return matchesSmt + && matchesEquipo + && matchesDepartamento + && matchesMesPlaneado + && matchesYear + && matchesEstado; } - private boolean matches(String value, String searchTerm) { - return searchTerm == null || searchTerm.isEmpty() - || value.toLowerCase().contains(searchTerm.toLowerCase()); + private boolean matches(String value, String serachTerm) { + return serachTerm == null || serachTerm.isEmpty() + || (value != null && value.toLowerCase().contains(serachTerm.toLowerCase())); } } diff --git a/src/main/resources/META-INF/resources/images/LOGO_1024X768.jpg b/src/main/resources/META-INF/resources/images/LOGO_1024X768.jpg new file mode 100644 index 0000000..7e63a9b Binary files /dev/null and b/src/main/resources/META-INF/resources/images/LOGO_1024X768.jpg differ diff --git a/src/main/resources/META-INF/resources/images/imgCorreo/MttoRealizado.png b/src/main/resources/META-INF/resources/images/imgCorreo/MttoRealizado.png new file mode 100644 index 0000000..390473a Binary files /dev/null and b/src/main/resources/META-INF/resources/images/imgCorreo/MttoRealizado.png differ diff --git a/src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png b/src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png new file mode 100644 index 0000000..fffa464 Binary files /dev/null and b/src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png differ diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 5240f8b..c027611 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,4 +1,7 @@ #Configuracion de la base de datos db.url=jdbc:mysql://localhost:3307/mantenimientosdb db.user=mantenimientos -db.pass=mantenimientos \ No newline at end of file +db.pass=mantenimientos + +# URL base local +app.base-url=http://localhost:8080 \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index c908743..944847e 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -2,4 +2,7 @@ db.url=jdbc:mysql://db:3306/mantenimientosdb #db.url=jdbc:oracle:thin:@//SVRAPPS:1521/XEPDB1 db.user=mantenimientos -db.pass=mantenimientos \ No newline at end of file +db.pass=mantenimientos + +# URL base productivo +app.base-url=https://smt.jumapacelaya.gob.mx \ No newline at end of file