Merge pull request '#45-Registro Semanal de Horas Trabajadas' (#69) from hoursworked into main
All checks were successful
Builder / Build-Project (push) Successful in 2m47s

Reviewed-on: #69
This commit is contained in:
alex 2024-11-11 19:21:21 +00:00
commit 3b0e5833f8
12 changed files with 1005 additions and 454 deletions

855
package-lock.json generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -9,6 +9,8 @@ public final class Actividad {
private double viernes; private double viernes;
private double sabado; private double sabado;
private double domingo; private double domingo;
private String tarea;
private double horas;
public Actividad(final Builder builder) { public Actividad(final Builder builder) {
this.nombre = builder.nombre; this.nombre = builder.nombre;
@ -19,6 +21,8 @@ public final class Actividad {
this.viernes = builder.viernes; this.viernes = builder.viernes;
this.sabado = builder.sabado; this.sabado = builder.sabado;
this.domingo = builder.domingo; this.domingo = builder.domingo;
this.tarea = builder.tarea;
this.horas = builder.horas;
} }
public String getNombre() { public String getNombre() {
@ -53,6 +57,14 @@ public final class Actividad {
return domingo; return domingo;
} }
public String getTarea() { // Cambié aquí también
return tarea;
}
public double getHoras() {
return horas;
}
// Builder para crear instancias de Actividad // Builder para crear instancias de Actividad
public static class Builder { public static class Builder {
private String nombre; private String nombre;
@ -63,6 +75,19 @@ public final class Actividad {
private double viernes; private double viernes;
private double sabado; private double sabado;
private double domingo; private double domingo;
private String tarea; // Cambié 'tarea' por 'descripcion'
private double horas;
public Builder tarea(final String tarea, final double horas) {
this.tarea = tarea;
this.horas = horas;
return this;
}
public Builder tarea(final String tarea) {
this.tarea = tarea;
return this;
}
public Builder nombre(final String nombre) { public Builder nombre(final String nombre) {
this.nombre = nombre; this.nombre = nombre;
@ -108,4 +133,4 @@ public final class Actividad {
return new Actividad(this); return new Actividad(this);
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -10,4 +11,7 @@ public interface EmployeeRepository extends JpaRepository<Employee, UUID> {
Optional<Employee> findByUsername(String username); Optional<Employee> findByUsername(String username);
Optional<Employee> findByPersonalEmail(String personalEmail); Optional<Employee> findByPersonalEmail(String personalEmail);
Optional<Employee> findByTeamId(UUID teamId);
List<Employee> findByTeamName(String teamName);
} }

View File

@ -4,7 +4,11 @@ import com.primefactorsolutions.model.HoursWorked;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
@Repository @Repository
public interface HoursWorkedRepository extends JpaRepository<HoursWorked, Long> { public interface HoursWorkedRepository extends JpaRepository<HoursWorked, Long> {
// Puedes definir consultas personalizadas aquí si es necesario. // Puedes definir consultas personalizadas aquí si es necesario.
List<HoursWorked> findByWeekNumber(int weekNumber);
} }

View File

@ -48,6 +48,13 @@ public class EmployeeService {
return null; return null;
} }
public String getTeamLeadName(final UUID teamId) {
// Encuentra al empleado con el rol de lead_manager en el equipo especificado
Optional<Employee> leadManager = employeeRepository.findByTeamId(teamId);
return leadManager.map(employee -> employee.getFirstName() + " " + employee.getLastName())
.orElse("No asignado");
}
public List<Employee> findEmployees( public List<Employee> findEmployees(
final int start, final int pageSize, final String sortProperty, final boolean asc) { final int start, final int pageSize, final String sortProperty, final boolean asc) {
List<Employee> employees = employeeRepository.findAll(); List<Employee> employees = employeeRepository.findAll();
@ -115,4 +122,10 @@ public class EmployeeService {
public List<Employee> findAllEmployees() { public List<Employee> findAllEmployees() {
return employeeRepository.findAll(); return employeeRepository.findAll();
} }
public List<Employee> findEmployeesByTeam(final String teamName) {
return employeeRepository.findByTeamName(teamName);
}
} }

View File

@ -31,5 +31,10 @@ public class HoursWorkedService {
public void deleteHoursWorked(final Long id) { public void deleteHoursWorked(final Long id) {
hoursWorkedRepository.deleteById(id); hoursWorkedRepository.deleteById(id);
} }
public List<HoursWorked> findByWeekNumber(final int weekNumber) {
return hoursWorkedRepository.findByWeekNumber(weekNumber);
}
} }

View File

@ -11,6 +11,7 @@ import lombok.SneakyThrows;
import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,20 +32,59 @@ public class ReportService {
// Este método ahora solo crea el archivo Excel a partir de los datos que recibe. // Este método ahora solo crea el archivo Excel a partir de los datos que recibe.
public byte[] writeAsExcel(final String reportName, final List<String> headers, public byte[] writeAsExcel(final String reportName, final List<String> headers,
final List<Map<String, Object>> data) final List<Map<String, Object>> data, final String selectedTeam,
final int weekNumber, final int currentYear)
throws IOException { throws IOException {
return createExcelFile(reportName, headers, data); return createExcelFile(reportName, headers, data, selectedTeam, weekNumber, currentYear);
} }
private byte[] createExcelFile(final String reportName, final List<String> headers, private byte[] createExcelFile(final String reportName, final List<String> headers,
final List<Map<String, Object>> data) final List<Map<String, Object>> data, final String selectedTeam,
final int weekNumber, final int currentYear)
throws IOException { throws IOException {
try (Workbook workbook = new XSSFWorkbook(); try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream os = new ByteArrayOutputStream()) { ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet(reportName); Sheet sheet = workbook.createSheet(reportName);
// Crear encabezados // Crear encabezados
Row headerRow = sheet.createRow(0); // Crear una fila para el rótulo "Reporte por equipo"
Row titleRow = sheet.createRow(0); // Fila 0 para el rótulo
Cell titleCell = titleRow.createCell(0);
// Concatenar el nombre del equipo al rótulo
String titleText = "Informe: " + weekNumber + "/" + currentYear;
titleCell.setCellValue(titleText);
// Estilo del rótulo
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 14); // Tamaño de la fuente
titleStyle.setFont(titleFont);
titleCell.setCellStyle(titleStyle);
// Fusionar celdas para el rótulo
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1)); // Ajusta el rango de celdas
// Crear filas adicionales con la información solicitada
Row asuntoRow = sheet.createRow(1); // Fila 1: Asunto
asuntoRow.createCell(0).setCellValue("Asunto: Informe semanal de horas trabajadas");
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, headers.size() - 1));
Row semanaRow = sheet.createRow(2); // Fila 2: Semana
semanaRow.createCell(0).setCellValue("Semana: " + weekNumber); // Puedes insertar una fecha real aquí
sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, headers.size() - 1));
Row horasCumplirRow = sheet.createRow(3); // Fila 3: Horas a cumplir
horasCumplirRow.createCell(0).setCellValue("Horas a cumplir: 40 horas"); // Puedes agregar las horas reales
sheet.addMergedRegion(new CellRangeAddress(3, 3, 0, headers.size() - 1));
Row teamLeadRow = sheet.createRow(4); // Fila 4: Team Lead
teamLeadRow.createCell(0).setCellValue("Team Lead: "); // Solo texto
sheet.addMergedRegion(new CellRangeAddress(4, 4, 0, headers.size() - 1));
// Crear encabezados (fila 5)
Row headerRow = sheet.createRow(5); // Los encabezados empiezan en la fila 5
CellStyle headerStyle = workbook.createCellStyle(); CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont(); Font headerFont = workbook.createFont();
headerFont.setBold(true); headerFont.setBold(true);
@ -56,20 +96,22 @@ public class ReportService {
cell.setCellStyle(headerStyle); cell.setCellStyle(headerStyle);
} }
// Crear filas de datos // Crear filas de datos (a partir de la fila 6)
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
Row dataRow = sheet.createRow(i + 1); Row dataRow = sheet.createRow(i + 6); // Los datos empiezan después de la fila de encabezados
Map<String, Object> rowData = data.get(i); Map<String, Object> rowData = data.get(i);
int cellIndex = 0; int cellIndex = 0;
for (String key : headers) { for (String key : headers) {
Cell cell = dataRow.createCell(cellIndex++); Cell cell = dataRow.createCell(cellIndex++);
Object value = rowData.get(key); Object value = rowData.get(key);
switch (value) { if (value != null) {
case String s -> cell.setCellValue(s); if (value instanceof String) {
case Number number -> cell.setCellValue(number.doubleValue()); cell.setCellValue((String) value);
case null -> cell.setCellValue(""); // Manejo de valores nulos } else if (value instanceof Number) {
default -> { cell.setCellValue(((Number) value).doubleValue());
} }
} else {
cell.setCellValue(""); // Manejo de valores nulos
} }
} }
} }

View File

@ -1,5 +1,7 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.model.*; import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.ReportService; import com.primefactorsolutions.service.ReportService;

View File

@ -17,8 +17,11 @@ import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent; import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.html.Label;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.time.DayOfWeek; import java.time.DayOfWeek;
@ -36,33 +39,74 @@ import java.util.Locale;
@Route(value = "/hours-worked/me", layout = MainLayout.class) @Route(value = "/hours-worked/me", layout = MainLayout.class)
public class HoursWorkedView extends VerticalLayout { public class HoursWorkedView extends VerticalLayout {
private final List<Actividad> actividades = new ArrayList<>(); private final List<Actividad> actividades = new ArrayList<>();
private final List<Actividad> actividadesEspecificas = new ArrayList<>(); // Nueva lista para tareas específicas
private final Grid<Actividad> grid = new Grid<>(Actividad.class); private final Grid<Actividad> grid = new Grid<>(Actividad.class);
private final Grid<Actividad> gridActividadesEspecificas = new Grid<>(Actividad.class);
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Employee"); private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Employee");
private final ComboBox<String> equipoDropdown = new ComboBox<>("Equipo");
private final ComboBox<String> tareasEspecificasDropdown = new ComboBox<>("Tareas Específicas");
private final TextField tareaEspecificaInput = new TextField("Especificar Tarea");
private TextField horasTareaInput = crearCampoHora("Horas Tareas");
private LocalDate selectedStartOfWeek; private LocalDate selectedStartOfWeek;
private int weekNumber; private int weekNumber;
@Autowired @Autowired
private final EmployeeService employeeService; private final EmployeeService employeeService;
@Autowired
private final HoursWorkedService hoursWorkedService;
private final Label fechasLabel = new Label("Selecciona una semana para ver las fechas."); private final Label fechasLabel = new Label("Selecciona una semana para ver las fechas.");
private final Label totalCompletadoLabel = new Label(); private final Label totalCompletadoLabel = new Label();
private final Label horasPendientesLabel = new Label(); private final Label horasPendientesLabel = new Label();
private final Label numeroSemanaLabel = new Label("Número de la Semana: ");
private DatePicker fechaPicker = new DatePicker("Selecciona una fecha");
@Autowired @Autowired
private final HoursWorkedService hoursWorkedService; private InternalResourceViewResolver defaultViewResolver;
@Qualifier("defaultServletHandlerMapping")
@Autowired
private HandlerMapping defaultServletHandlerMapping;
public HoursWorkedView(final EmployeeService employeeService, final HoursWorkedService hoursWorkedService) { public HoursWorkedView(final EmployeeService employeeService, final HoursWorkedService hoursWorkedService) {
this.employeeService = employeeService; this.employeeService = employeeService;
this.hoursWorkedService = hoursWorkedService; this.hoursWorkedService = hoursWorkedService;
configurarVista(); configurarVista();
configurarGrid();
configurarGridActividadesEspecificas();
cargarDatos(); cargarDatos();
configurarTareasEspecificas();
}
private void configurarTareasEspecificas() {
tareasEspecificasDropdown.setItems("Entrevistas", "Reuniones", "Colaboraciones",
"Aprendizajes", "Proyectos PFS", "Otros");
tareasEspecificasDropdown.setPlaceholder("Selecciona una tarea...");
tareasEspecificasDropdown.addValueChangeListener(event -> {
String selected = event.getValue();
// Activa el campo de texto si la opción seleccionada es "Otros"
tareaEspecificaInput.setVisible("Otros".equals(selected));
if (!"Otros".equals(selected)) {
tareaEspecificaInput.clear();
}
});
tareaEspecificaInput.setVisible(false);
} }
private void cargarDatos() { private void cargarDatos() {
List<HoursWorked> listaDeHorasTrabajadas = obtenerDatos(); // Obtenemos la lista aquí if (selectedStartOfWeek != null && weekNumber > 0) {
grid.setItems(actividades); actividades.clear();
actividadesEspecificas.clear();
double totalHoras = calcularTotalHoras(listaDeHorasTrabajadas); // Pasa la lista aquí List<HoursWorked> listaDeHorasTrabajadas = obtenerDatos();
grid.setItems(actividades);
gridActividadesEspecificas.setItems(actividadesEspecificas);
calcularTotalHoras(listaDeHorasTrabajadas);
}
} }
private void setEmployeeComboBoxProperties() { private void setEmployeeComboBoxProperties() {
@ -70,7 +114,6 @@ public class HoursWorkedView extends VerticalLayout {
employeeComboBox.setPlaceholder("Buscar empleado..."); employeeComboBox.setPlaceholder("Buscar empleado...");
employeeComboBox.setItems(employeeService.findAllEmployees()); employeeComboBox.setItems(employeeService.findAllEmployees());
employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName()); employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName());
employeeComboBox.setAllowCustomValue(false); employeeComboBox.setAllowCustomValue(false);
employeeComboBox.addCustomValueSetListener(event -> employeeComboBox.addCustomValueSetListener(event ->
Notification.show("Selecciona un empleado válido de la lista.") Notification.show("Selecciona un empleado válido de la lista.")
@ -91,14 +134,15 @@ public class HoursWorkedView extends VerticalLayout {
} }
private void configurarVista() { private void configurarVista() {
DatePicker fechaPicker = new DatePicker("Selecciona una fecha");
fechaPicker.addValueChangeListener(event -> { fechaPicker.addValueChangeListener(event -> {
LocalDate selectedDate = event.getValue(); LocalDate selectedDate = event.getValue();
if (selectedDate != null) { if (selectedDate != null) {
selectedStartOfWeek = getStartOfWeek(selectedDate); selectedStartOfWeek = getStartOfWeek(selectedDate);
LocalDate endOfWeek = selectedStartOfWeek.plusDays(6); LocalDate endOfWeek = selectedStartOfWeek.plusDays(6);
fechasLabel.setText("Semana del " + selectedStartOfWeek + " al " + endOfWeek);
weekNumber = getWeekOfYear(selectedDate); weekNumber = getWeekOfYear(selectedDate);
fechasLabel.setText("Semana del " + selectedStartOfWeek + " al " + endOfWeek);
numeroSemanaLabel.setText("Número de la Semana: " + weekNumber);
cargarDatos();
} }
}); });
@ -106,17 +150,26 @@ public class HoursWorkedView extends VerticalLayout {
getUI().ifPresent(ui -> ui.navigate(HoursWorkedMonthView.class)); getUI().ifPresent(ui -> ui.navigate(HoursWorkedMonthView.class));
}); });
ComboBox<String> equipoDropdown = new ComboBox<>("Equipo"); equipoDropdown.setItems("ABC", "DEF", "XYZ");
equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3");
equipoDropdown.setWidth("250px"); equipoDropdown.setWidth("250px");
equipoDropdown.addValueChangeListener(event -> {
String selectedEquipo = event.getValue();
if (selectedEquipo != null) {
// Filtra la lista de empleados según el equipo seleccionado
List<Employee> filteredEmployees = employeeService.findEmployeesByTeam(selectedEquipo);
employeeComboBox.setItems(filteredEmployees);
}
});
setEmployeeComboBoxProperties(); setEmployeeComboBoxProperties();
HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, employeeComboBox); HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, employeeComboBox);
filtersLayout.setSpacing(true); filtersLayout.setSpacing(true);
configurarGrid();
HorizontalLayout actividadFormLayout = configurarFormularioActividades(); HorizontalLayout actividadFormLayout = configurarFormularioActividades();
HorizontalLayout tareasEspecificasLayout = configurarTareasEspecificasLayout();
Button actualizarButton = new Button("Actualizar Totales", event -> actualizarTotales()); Button actualizarButton = new Button("Actualizar Totales", event -> actualizarTotales());
Button guardarButton = new Button("Guardar", event -> guardarActividades()); Button guardarButton = new Button("Guardar", event -> guardarActividades());
@ -129,8 +182,8 @@ public class HoursWorkedView extends VerticalLayout {
totalesLayout.setSpacing(true); totalesLayout.setSpacing(true);
totalesLayout.setPadding(true); totalesLayout.setPadding(true);
add(fechaPicker, fechasLabel, filtersLayout, grid, actividadFormLayout, buttonsLayout, totalesLayout); add(fechaPicker, fechasLabel, numeroSemanaLabel, filtersLayout, actividadFormLayout,
tareasEspecificasLayout, grid, gridActividadesEspecificas, buttonsLayout, totalesLayout);
} }
private void configurarGrid() { private void configurarGrid() {
@ -147,52 +200,142 @@ public class HoursWorkedView extends VerticalLayout {
grid.addColumn(this::calcularTotalPorDia).setHeader("Total Día").setKey("totalDia"); grid.addColumn(this::calcularTotalPorDia).setHeader("Total Día").setKey("totalDia");
} }
private void configurarGridActividadesEspecificas() {
gridActividadesEspecificas.removeAllColumns();
gridActividadesEspecificas.setItems(actividadesEspecificas);
gridActividadesEspecificas.addColumn(Actividad::getTarea).setHeader("Actividad");
gridActividadesEspecificas.addColumn(Actividad::getLunes).setHeader("Lunes");
gridActividadesEspecificas.addColumn(Actividad::getMartes).setHeader("Martes");
gridActividadesEspecificas.addColumn(Actividad::getMiercoles).setHeader("Miércoles");
gridActividadesEspecificas.addColumn(Actividad::getJueves).setHeader("Jueves");
gridActividadesEspecificas.addColumn(Actividad::getViernes).setHeader("Viernes");
gridActividadesEspecificas.addColumn(Actividad::getSabado).setHeader("Sábado");
gridActividadesEspecificas.addColumn(Actividad::getDomingo).setHeader("Domingo");
gridActividadesEspecificas.addColumn(this::calcularTotalPorDia).setHeader("Total Día Específico")
.setKey("totalDiaEspecifico");
}
private HorizontalLayout configurarFormularioActividades() { private HorizontalLayout configurarFormularioActividades() {
TextField actividadNombre = new TextField("Actividad"); TextField actividadNombre = new TextField("Actividad");
actividadNombre.setWidth("200px"); actividadNombre.setWidth("200px");
TextField lunesHoras = crearCampoHora("Lunes"); TextField horasInput = crearCampoHora("Horas");
TextField martesHoras = crearCampoHora("Martes");
TextField miercolesHoras = crearCampoHora("Miércoles");
TextField juevesHoras = crearCampoHora("Jueves");
TextField viernesHoras = crearCampoHora("Viernes");
TextField sabadoHoras = crearCampoHora("Sábado");
TextField domingoHoras = crearCampoHora("Domingo");
Button agregarActividadButton = new Button("Agregar Actividad", e -> { Button agregarActividadButton = new Button("Agregar Actividad", e -> {
try { try {
Actividad nuevaActividad = new Actividad.Builder() LocalDate selectedDate = fechaPicker.getValue();
.nombre(actividadNombre.getValue()) if (selectedDate == null) {
.lunes(parseHoras(lunesHoras.getValue())) Notification.show("Por favor, selecciona una fecha.");
.martes(parseHoras(martesHoras.getValue())) return;
.miercoles(parseHoras(miercolesHoras.getValue())) }
.jueves(parseHoras(juevesHoras.getValue())) DayOfWeek selectedDay = selectedDate.getDayOfWeek();
.viernes(parseHoras(viernesHoras.getValue()))
.sabado(parseHoras(sabadoHoras.getValue()))
.domingo(parseHoras(domingoHoras.getValue()))
.build();
actividades.add(nuevaActividad); Actividad.Builder actividadBuilder = new Actividad.Builder()
grid.setItems(actividades); .nombre(actividadNombre.getValue());
double horas = parseHoras(horasInput.getValue());
switch (selectedDay) {
case MONDAY -> actividadBuilder.lunes(horas);
case TUESDAY -> actividadBuilder.martes(horas);
case WEDNESDAY -> actividadBuilder.miercoles(horas);
case THURSDAY -> actividadBuilder.jueves(horas);
case FRIDAY -> actividadBuilder.viernes(horas);
case SATURDAY -> actividadBuilder.sabado(horas);
case SUNDAY -> actividadBuilder.domingo(horas);
default -> throw new IllegalArgumentException("Día seleccionado no válido: " + selectedDay);
}
String tareaSeleccionada = tareasEspecificasDropdown.getValue();
double horasTarea = parseHoras(horasTareaInput.getValue());
if (tareaSeleccionada != null && !tareaSeleccionada.isEmpty()) {
actividadBuilder.tarea(tareaSeleccionada).lunes(horasTarea);
Actividad nuevaActividadEspecifica = actividadBuilder.build();
actividadesEspecificas.add(nuevaActividadEspecifica);
gridActividadesEspecificas.setItems(actividadesEspecificas);
} else {
Actividad nuevaActividad = actividadBuilder.build();
actividades.add(nuevaActividad);
grid.setItems(actividades);
}
actualizarTotales(); actualizarTotales();
Notification.show("Actividad agregada correctamente"); Notification.show("Actividad agregada correctamente");
// Limpiar los campos de entrada
actividadNombre.clear(); actividadNombre.clear();
lunesHoras.clear(); horasInput.clear();
martesHoras.clear(); tareasEspecificasDropdown.clear();
miercolesHoras.clear(); horasTareaInput.clear();
juevesHoras.clear();
viernesHoras.clear();
sabadoHoras.clear();
domingoHoras.clear();
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
Notification.show("Error: Por favor ingresa números válidos para las horas."); Notification.show("Error: Por favor ingresa números válidos para las horas.");
} }
}); });
return new HorizontalLayout( return new HorizontalLayout(actividadNombre, horasInput, agregarActividadButton);
actividadNombre, lunesHoras, martesHoras, miercolesHoras, }
juevesHoras, viernesHoras, sabadoHoras, domingoHoras, agregarActividadButton);
private HorizontalLayout configurarTareasEspecificasLayout() {
Button agregarTareaButton = new Button("Agregar Tarea PFS", e -> {
try {
String tareaSeleccionada = tareasEspecificasDropdown.getValue();
String tareaNombre = "Otros"
.equals(tareaSeleccionada) ? tareaEspecificaInput
.getValue() : tareaSeleccionada;
if (tareaNombre == null || tareaNombre.isEmpty()) {
Notification.show("Por favor, especifica la tarea.");
return;
}
double horasTarea = parseHoras(horasTareaInput.getValue());
if (horasTarea <= 0) {
Notification.show("Por favor, ingresa un número válido para las horas.");
return;
}
if (tareaSeleccionada != null && !tareaSeleccionada.isEmpty() && horasTarea > 0) {
LocalDate selectedDate = fechaPicker.getValue();
if (selectedDate == null) {
Notification.show("Selecciona una fecha para asignar la tarea.");
return;
}
DayOfWeek selectedDay = selectedDate.getDayOfWeek();
Actividad.Builder actividadBuilder = new Actividad.Builder().tarea(tareaNombre);
switch (selectedDay) {
case MONDAY -> actividadBuilder.lunes(horasTarea);
case TUESDAY -> actividadBuilder.martes(horasTarea);
case WEDNESDAY -> actividadBuilder.miercoles(horasTarea);
case THURSDAY -> actividadBuilder.jueves(horasTarea);
case FRIDAY -> actividadBuilder.viernes(horasTarea);
case SATURDAY -> actividadBuilder.sabado(horasTarea);
case SUNDAY -> actividadBuilder.domingo(horasTarea);
default -> throw new IllegalArgumentException("Día seleccionado no válido: " + selectedDay);
}
Actividad nuevaActividadEspecifica = actividadBuilder.build();
actividadesEspecificas.add(nuevaActividadEspecifica);
gridActividadesEspecificas.setItems(actividadesEspecificas);
actualizarTotales();
tareasEspecificasDropdown.clear();
tareaEspecificaInput.clear();
horasTareaInput.clear();
Notification.show("Tarea específica agregada correctamente");
} else {
Notification.show("Selecciona una tarea y asegúrate de ingresar horas válidas.");
}
} catch (NumberFormatException ex) {
Notification.show("Error: Por favor ingresa un número válido para las horas.");
}
});
HorizontalLayout layout = new HorizontalLayout(tareasEspecificasDropdown,
tareaEspecificaInput, horasTareaInput, agregarTareaButton);
layout.setSpacing(true);
return layout;
} }
private TextField crearCampoHora(final String placeholder) { private TextField crearCampoHora(final String placeholder) {
@ -220,20 +363,25 @@ public class HoursWorkedView extends VerticalLayout {
} }
private void actualizarTotales() { private void actualizarTotales() {
double totalSemanaCompletada = actividades.stream() double totalActividadesGenerales = actividades.stream()
.mapToDouble(this::calcularTotalPorDia) .mapToDouble(this::calcularTotalPorDia)
.sum(); .sum();
double horasPendientes = 40 - totalSemanaCompletada; double totalActividadesEspecificas = actividadesEspecificas.stream()
.mapToDouble(this::calcularTotalPorDia)
.sum();
double totalFinal = totalActividadesGenerales + totalActividadesEspecificas;
double horasPendientes = 40 - totalFinal;
totalCompletadoLabel.setText("Total Hrs/Semana Completadas: " + totalSemanaCompletada); totalCompletadoLabel.setText("Total Hrs/Semana Completadas: " + totalFinal);
horasPendientesLabel.setText("Horas Pendientes: " + horasPendientes); horasPendientesLabel.setText("Horas Pendientes: " + horasPendientes);
} }
private void guardarActividades() { private void guardarActividades() {
Employee selectedEmployee = employeeComboBox.getValue(); Employee selectedEmployee = employeeComboBox.getValue();
String selectedEquipo = equipoDropdown.getValue();
if (selectedEmployee == null) { if (selectedEmployee == null || selectedEquipo == null) {
Notification.show("Por favor, selecciona un empleado antes de guardar."); Notification.show("Por favor selecciona un equipo y un empleado.");
return; return;
} }
@ -249,6 +397,7 @@ public class HoursWorkedView extends VerticalLayout {
try { try {
hoursWorkedService.saveHoursWorked(hoursWorked); // Usa saveHoursWorked directamente hoursWorkedService.saveHoursWorked(hoursWorked); // Usa saveHoursWorked directamente
Notification.show("Actividades guardadas correctamente."); Notification.show("Actividades guardadas correctamente.");
System.out.println(hoursWorked);
} catch (Exception e) { } catch (Exception e) {
Notification.show("Error al guardar actividades: " + e.getMessage()); Notification.show("Error al guardar actividades: " + e.getMessage());
} }

View File

@ -150,7 +150,9 @@ public class MainLayout extends AppLayout {
LineAwesomeIcon.LIST_ALT.create())); LineAwesomeIcon.LIST_ALT.create()));
SideNavItem timesheet = new SideNavItem("My Timesheet", TimesheetView.class, SideNavItem timesheet = new SideNavItem("My Timesheet", TimesheetView.class,
LineAwesomeIcon.HOURGLASS_START_SOLID.create()); LineAwesomeIcon.HOURGLASS_START_SOLID.create());
timesheet.addItem(new SideNavItem("Hours Worked", HoursWorkedView.class, timesheet.addItem(new SideNavItem("Horas Trabajadas", HoursWorkedView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
timesheet.addItem(new SideNavItem("Reporte Horas Trabajadas", ReporteView.class,
LineAwesomeIcon.ID_CARD_SOLID.create())); LineAwesomeIcon.ID_CARD_SOLID.create()));
SideNavItem profile = new SideNavItem("My Profile", ProfileView.class, SideNavItem profile = new SideNavItem("My Profile", ProfileView.class,

View File

@ -1,22 +1,34 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.primefactorsolutions.model.HoursWorked; import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.HoursWorkedService; import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.ReportService; import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Anchor; import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import com.vaadin.flow.component.notification.Notification; import com.primefactorsolutions.service.EmployeeService;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -25,63 +37,217 @@ import java.util.stream.Collectors;
@PageTitle("Reporte de Horas Trabajadas") @PageTitle("Reporte de Horas Trabajadas")
public class ReporteView extends VerticalLayout { public class ReporteView extends VerticalLayout {
private final EmployeeService employeeService;
private final HoursWorkedService hoursWorkedService; private final HoursWorkedService hoursWorkedService;
private final ReportService reportService; private final ReportService reportService;
private final TeamService teamService;
private final ComboBox<Team> equipoComboBox = new ComboBox<>("Seleccionar Equipo");
private final ComboBox<String> semanaComboBox = new ComboBox<>("Seleccionar Semana");
private final Grid<Map<String, Object>> grid = new Grid<>();
private final VerticalLayout headerLayout = new VerticalLayout();
private Anchor downloadLink;
private final Span semanaInfoSpan = new Span();
// Obtener el año actual
private int currentYear = LocalDate.now().getYear();
@Autowired @Autowired
public ReporteView(final HoursWorkedService hoursWorkedService, final ReportService reportService) { public ReporteView(final HoursWorkedService hoursWorkedService,
final ReportService reportService, final TeamService teamService,
final EmployeeService employeeService) {
this.hoursWorkedService = hoursWorkedService; this.hoursWorkedService = hoursWorkedService;
this.reportService = reportService; this.reportService = reportService;
this.teamService = teamService;
this.employeeService = employeeService;
H2 title = new H2("Reporte de Horas Trabajadas"); H2 title = new H2("Reporte de Horas Trabajadas");
add(title); add(title);
Button reportButton = new Button("Generar Reporte de Horas Trabajadas", event -> generateHoursWorkedReport()); List<Team> teams = teamService.findAllTeams();
add(reportButton); equipoComboBox.setItems(teams);
equipoComboBox.setItemLabelGenerator(Team::getName);
// Configurar el ComboBox de semanas
initializeSemanaComboBox();
// Listener para actualizar `semanaInfoSpan` con la selección del usuario en `semanaComboBox`
semanaComboBox.addValueChangeListener(event -> {
String selectedWeek = event.getValue();
semanaInfoSpan.setText(selectedWeek != null
? selectedWeek : "Selecciona una semana");
});
Button reportButton = new Button("Generar Reporte de Horas Trabajadas",
event -> generateHoursWorkedReport());
HorizontalLayout filtersLayout = new HorizontalLayout(equipoComboBox,
semanaComboBox, reportButton);
add(filtersLayout);
// Añadir `headerLayout` al diseño principal para el encabezado dinámico
add(headerLayout);
updateHeaderLayout(null, null);
grid.addColumn(map -> map.get("ID")).setHeader("ID")
.getElement().getStyle().set("font-weight", "bold");
grid.addColumn(map -> map.get("Employee ID")).setHeader("Employee ID");
grid.addColumn(map -> map.get("Empleado")).setHeader("Empleado");
grid.addColumn(map -> map.get("Horas Trabajadas")).setHeader("Horas Trabajadas");
grid.addColumn(map -> map.get("Horas Pendientes")).setHeader("Horas Pendientes");
grid.addColumn(map -> map.get("Observaciones")).setHeader("Observaciones");
add(grid);
}
private void initializeSemanaComboBox() {
int year = LocalDate.now().getYear();
LocalDate startOfYear = LocalDate.of(year, 1, 5); // Suponemos que la semana comienza el 5 de enero.
List<String> semanas = startOfYear.datesUntil(LocalDate
.of(year + 1, 1, 1),
java.time.Period.ofWeeks(1))
.map(date -> {
int weekNumber = date.get(WeekFields.of(DayOfWeek.MONDAY, 1)
.weekOfWeekBasedYear());
LocalDate startOfWeek = date;
LocalDate endOfWeek = startOfWeek.plusDays(6);
return String.format("Semana %d: %s - %s",
weekNumber,
startOfWeek.getDayOfMonth() + " de " + startOfWeek.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault()),
endOfWeek.getDayOfMonth() + " de " + endOfWeek.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault())
);
})
.collect(Collectors.toList());
semanaComboBox.setItems(semanas);
semanaComboBox.setPlaceholder("Seleccione una semana");
} }
private void generateHoursWorkedReport() { private void generateHoursWorkedReport() {
List<HoursWorked> hoursWorkedList = hoursWorkedService.findAll(); // Obtener la lista de HoursWorked Team selectedEquipo = equipoComboBox.getValue();
String selectedWeek = semanaComboBox.getValue();
if (hoursWorkedList.isEmpty()) { if (selectedEquipo == null || selectedWeek == null) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.", Notification.show("Por favor, selecciona un equipo y una semana para generar el reporte.",
3000, Notification.Position.MIDDLE); 3000,
Notification.Position.MIDDLE);
return; return;
} }
try { int weekNumber = Integer.parseInt(selectedWeek.split(" ")[1]
List<String> headers = List.of("ID", "Employee ID", "Week Number", "Total Hours"); .replace(":", ""));
LocalDate selectedDate = LocalDate.now()
.with(WeekFields.of(DayOfWeek.FRIDAY, 1)
.weekOfWeekBasedYear(), weekNumber);
updateHeaderLayout(selectedEquipo, selectedDate);
List<Map<String, Object>> data = hoursWorkedList.stream() List<HoursWorked> hoursWorkedList = hoursWorkedService.findAll().stream()
.map(hoursWorked -> { .filter(hw -> hw.getEmployee().getTeam().getId()
Map<String, Object> map = new HashMap<>(); .equals(selectedEquipo.getId()) && hw.getWeekNumber() == weekNumber)
map.put("ID", hoursWorked.getId().toString()); .collect(Collectors.toList());
map.put("Employee ID", hoursWorked.getEmployee().getId().toString());
map.put("Week Number", hoursWorked.getWeekNumber());
map.put("Total Hours", hoursWorked.getTotalHours());
return map;
})
.collect(Collectors.toList());
byte[] excelBytes = reportService.writeAsExcel("hours_worked_report", headers, data); if (hoursWorkedList.isEmpty()) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.",
3000,
Notification.Position.MIDDLE);
return;
}
StreamResource excelResource = new StreamResource("hours_worked_report.xlsx", List<Map<String, Object>> data = hoursWorkedList.stream()
() -> new ByteArrayInputStream(excelBytes)); .map(hoursWorked -> {
excelResource.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); Map<String, Object> map = new HashMap<>();
excelResource.setCacheTime(0); map.put("ID", hoursWorked.getId().toString());
map.put("Employee ID", hoursWorked.getEmployee().getId().toString());
map.put("Empleado", hoursWorked.getEmployee().getFirstName() + " "
+ hoursWorked.getEmployee().getLastName());
map.put("Horas Trabajadas", hoursWorked.getTotalHours());
map.put("Horas Pendientes", 40 - hoursWorked.getTotalHours());
map.put("Observaciones", "");
return map;
})
.collect(Collectors.toList());
Anchor downloadLink = new Anchor(excelResource, "Descargar Reporte de Horas Trabajadas"); grid.setItems(data);
downloadLink.getElement().setAttribute("download", true); generateExcelDownloadLink(data, weekNumber);
}
add(downloadLink); private void updateHeaderLayout(final Team team, final LocalDate dateInWeek) {
Notification.show("Reporte de horas trabajadas generado exitosamente.", headerLayout.removeAll();
3000, Notification.Position.MIDDLE);
} catch (Exception e) { if (team != null && dateInWeek != null) {
Notification.show("Error al generar el reporte de horas trabajadas. Inténtalo de nuevo.", LocalDate startOfWeek = dateInWeek.with(DayOfWeek.FRIDAY);
3000, Notification.Position.MIDDLE); LocalDate endOfWeek = dateInWeek.with(DayOfWeek.THURSDAY);
e.printStackTrace(); int weekNumber = getWeekOfYear(dateInWeek);
String formattedStartDate = startOfWeek.getDayOfMonth() + " de "
+ startOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault());
String formattedEndDate = endOfWeek.getDayOfMonth() + " de "
+ endOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault());
headerLayout.add(new Span("Informe " + String.format("%03d", weekNumber)
+ "/" + currentYear) {{
getStyle().set("font-size", "24px");
getStyle().set("font-weight", "bold");
}});
String teamLeadName = employeeService.getTeamLeadName(team.getId());
headerLayout.add(
new Span("Asunto: Informe Semanal de Horas Trabajadas") {{
getStyle().set("font-size", "18px");
}},
semanaInfoSpan,
new Span("Horas a cumplir: 40 horas") {{
getStyle().set("font-size", "18px");
}},
new Span("Equipo: " + team.getName()) {{
getStyle().set("font-size", "18px");
}},
new Span("Team Lead: " + teamLeadName) {{
getStyle().set("font-size", "18px");
}}
);
} }
} }
private void generateExcelDownloadLink(final List<Map<String, Object>> data,
final int weekNumber) {
try {
List<String> headers = List.of("ID", "Employee ID", "Empleado",
"Horas Trabajadas", "Horas Pendientes", "Observaciones");
String selectedTeam = equipoComboBox.getValue().getName();
byte[] excelBytes = reportService.writeAsExcel("hours_worked_report",
headers, data, selectedTeam, weekNumber, currentYear);
StreamResource excelResource = new StreamResource("hours_worked_report.xlsx",
() -> new ByteArrayInputStream(excelBytes));
if (downloadLink == null) {
downloadLink = new Anchor(excelResource, "Descargar Reporte en Excel");
downloadLink.getElement().setAttribute("download", true);
add(downloadLink);
} else {
downloadLink.setHref(excelResource);
}
} catch (Exception e) {
Notification.show("Error al generar el reporte de horas trabajadas en Excel.",
3000, Notification.Position.MIDDLE);
}
}
private int getWeekOfYear(final LocalDate date) {
return date.get(WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear());
}
public int getCurrentYear() {
return currentYear;
}
// Opcional: Si deseas permitir cambiar el año actual manualmente, agrega un setter
public void setCurrentYear(final int currentYear) {
this.currentYear = currentYear;
}
} }