Browse Source

Solucion de historias no relacionadas y fusion de camnios en la rama main

master
mramirezg 1 week ago
parent
commit
2a3838902f
19 changed files with 1403 additions and 356 deletions
  1. BIN
      src/main/bundles/dev.bundle
  2. +24
    -2
      src/main/frontend/themes/sistema-mantenimiento/styles.css
  3. +169
    -70
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  4. +12
    -1
      src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java
  5. +87
    -32
      src/main/java/mx/gob/jumapacelaya/models/Ticket.java
  6. +48
    -0
      src/main/java/mx/gob/jumapacelaya/models/encuestas/Pregunta.java
  7. +66
    -0
      src/main/java/mx/gob/jumapacelaya/models/encuestas/Respuesta.java
  8. +168
    -24
      src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java
  9. +0
    -1
      src/main/java/mx/gob/jumapacelaya/services/EmailService.java
  10. +319
    -59
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java
  11. +206
    -143
      src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java
  12. +216
    -0
      src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java
  13. +1
    -1
      src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java
  14. +79
    -21
      src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java
  15. BIN
      src/main/resources/META-INF/resources/images/LOGO_1024X768.jpg
  16. BIN
      src/main/resources/META-INF/resources/images/imgCorreo/MttoRealizado.png
  17. BIN
      src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png
  18. +4
    -1
      src/main/resources/application-dev.properties
  19. +4
    -1
      src/main/resources/application-prod.properties

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


+ 24
- 2
src/main/frontend/themes/sistema-mantenimiento/styles.css View File

@ -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) {


+ 169
- 70
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -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<Ticket> getTickets(RedmineUser user, boolean includeClosed) {
public List<Ticket> getTickets(RedmineUser user, boolean includeClosed,
int offset, int limit,
LocalDate fechaDesde,
LocalDate fechaHasta) {
List<Ticket> 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<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String responseBody = response.body();
List<Ticket> 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<String> 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<String> 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<Ticket> getTicketsAuthor(RedmineUser user, boolean includeClosed) {
/*public List<Ticket> getTicketsAuthor(RedmineUser user, boolean includeClosed) {
List<Ticket> 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<LocalDateTime>) (src, typeOfSrc, context) ->
new JsonPrimitive(src.toString()))
.registerTypeAdapter(LocalDateTime.class,
(JsonDeserializer<LocalDateTime>) (json, typeOf, context) ->
LocalDateTime.parse(json.getAsString()))
.setPrettyPrinting()
.create();
// Método auxiliar para parsear echas con hora
private LocalDateTime parseDateTime(JsonObject issue, String fieldName) {
if (!issue.has(fieldName) || issue.get(fieldName).isJsonNull()) {
return null;
}
String dateStr = issue.get(fieldName).getAsString();
try {
// Si viene con 'Z' al final (UTC), convertir a LocalDateTime
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr);
}
} catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
return null;
}
}
public List<Ticket> getAllTickets(RedmineUser user, boolean abiertos,
LocalDate fechaDesde, LocalDate fechaHasta) {
List<Ticket> allTickets = new ArrayList<>();
int offset = 0;
int limit = 100; // Redmine suele permitir hasta 100 por request (puedes probar 1000)
List<Ticket> batch;
do {
batch = getTickets(user, abiertos, offset, limit, fechaDesde, fechaHasta);
allTickets.addAll(batch);
offset += limit;
} while (!batch.isEmpty());
return allTickets;
}
}

+ 12
- 1
src/main/java/mx/gob/jumapacelaya/models/PlanAnual.java View File

@ -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;
}
}

+ 87
- 32
src/main/java/mx/gob/jumapacelaya/models/Ticket.java View File

@ -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<DateTimeFormatter> 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";
}


+ 48
- 0
src/main/java/mx/gob/jumapacelaya/models/encuestas/Pregunta.java View File

@ -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;
}
}

+ 66
- 0
src/main/java/mx/gob/jumapacelaya/models/encuestas/Respuesta.java View File

@ -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;
}
}

+ 168
- 24
src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java View File

@ -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<PlanAnual> getPlanAnual() {
List<PlanAnual> 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<Pregunta> getPreguntas() {
List<Pregunta> 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<Respuesta> 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;
}
/***************************************************************************************************************************************/
}

+ 0
- 1
src/main/java/mx/gob/jumapacelaya/services/EmailService.java View File

@ -36,7 +36,6 @@ public class EmailService {
helper.addInline("image_id", imgResource);
mailSender.send(mensaje);
System.out.println("Correo enviado con imagen exitosamente");


+ 319
- 59
src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java View File

@ -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<Ticket> 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<String> columns = List.of("tipo","tiempoEst","asunto","autor","estado","fechaCreacion","fechaCierre","fechaActualizacion","duracion");
CheckboxGroup<String> 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<String> 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<Ticket> tickets = redmineClient.getTickets(userService.getRedmineUser(), true);
List<Ticket> 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<Span, Ticket> 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<Button, Ticket> buttonTicketComponentRenderer() {
/*public ComponentRenderer<Button, Ticket> 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<Ticket> 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);
}
}

+ 206
- 143
src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java View File

@ -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<TiposMantenimiento> cbTipo;
private TextField txtFecha;
private TextField txtFechaProgramada;
private TextField txtSituacion;
private ComboBox<Usuario> cbUsuario;
private ComboBox<DepartamentosModel> cbDepartamento;
private GridPro<HardwareDetalle> gridHardware;
private GridPro<ActualizacioneSeguridadModel> gridActualizaciones;
private Button btnEditar;
private Button btnEditarFirmas;
private Button btnImprimirRepo;
private Button btnCancelar;
private Button btnGuardar;
private final TextField txtEquipo;
private final ComboBox<TiposMantenimiento> cbTipo;
private final TextField txtFecha;
private final TextField txtFechaProgramada;
private final TextField txtSituacion;
private final ComboBox<Usuario> cbUsuario;
private final ComboBox<DepartamentosModel> cbDepartamento;
private final GridPro<HardwareDetalle> gridHardware;
private final GridPro<ActualizacioneSeguridadModel> 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 = "<html>" +
"<body>" +
"<a href = "+linkEncuesta+">" +
"<img src='cid:image_id'/>"+
"</a>" +
"</body>" +
"</html>";
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<HardwareDetalle> 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();


+ 216
- 0
src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java View File

@ -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<String> 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<String> radios;
RespuestaComponente(Pregunta pregunta, RadioButtonGroup<String> radios) {
this.pregunta = pregunta;
this.radios = radios;
}
}
private final List<RespuestaComponente> 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<Pregunta> 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<String> 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<Respuesta> 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);
}
}
}

+ 1
- 1
src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java View File

@ -565,7 +565,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
"</body>" +
"</html>";
String imagePath = "META-INF/resources/images/imgCorreo/correoMantt.png";
String imagePath = "META-INF/resources/images/imgCorreo/MttoRealizado.png";
emailService.enviarCorreo(destinatario, asunto, cuerpo, imagePath);


+ 79
- 21
src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java View File

@ -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<Integer> yearFilter;
public PlanAnualView(DatabaseService databaseService, Environment env, ReportService reportService) {
this.databaseService = databaseService;
@ -104,11 +109,14 @@ public class PlanAnualView extends VerticalLayout {
GridListDataView<PlanAnual> 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, String>) PlanAnual::getSituacion)
.setHeader("Situación").setAutoWidth(true).setKey("situacion");
planAnualGrid.addColumn((ValueProvider<PlanAnual, String>) PlanAnual::getEncuesta)
.setHeader("Encuesta").setAutoWidth(true).setKey("encuesta");
/*planAnualGrid.addColumn((ValueProvider<PlanAnual, String>) 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<Integer> years = IntStream.rangeClosed(currentYear - 1, currentYear + 4)
.boxed().collect(Collectors.toList());
yearFilter.setItems(years);
yearFilter.setPlaceholder("Año");
yearFilter.setClearButtonVisible(true);
List<PlanAnual> 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<String> 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<PlanAnual> dataView;
private String smt;
private String equipo;
private String departamento;
private String mesPlaneado;
private Integer year;
private boolean excludeRealizado = true;
public PlanAnualFilter(GridListDataView<PlanAnual> 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()));
}
}


BIN
src/main/resources/META-INF/resources/images/LOGO_1024X768.jpg View File

Before After
Width: 1025  |  Height: 769  |  Size: 119 KiB

BIN
src/main/resources/META-INF/resources/images/imgCorreo/MttoRealizado.png View File

Before After
Width: 1024  |  Height: 1536  |  Size: 3.2 MiB

BIN
src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png View File

Before After
Width: 1024  |  Height: 1536  |  Size: 3.2 MiB

+ 4
- 1
src/main/resources/application-dev.properties View File

@ -1,4 +1,7 @@
#Configuracion de la base de datos
db.url=jdbc:mysql://localhost:3307/mantenimientosdb
db.user=mantenimientos
db.pass=mantenimientos
db.pass=mantenimientos
# URL base local
app.base-url=http://localhost:8080

+ 4
- 1
src/main/resources/application-prod.properties View File

@ -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
db.pass=mantenimientos
# URL base productivo
app.base-url=https://smt.jumapacelaya.gob.mx

Loading…
Cancel
Save