Resolviendo conflictos y finalizando fusión entre ramas Vacaciones y En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s

This commit is contained in:
jesus.pelaez 2024-11-06 14:55:53 -04:00
commit ed88936683
26 changed files with 1962 additions and 953 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ drivers/
# Error screenshots generated by TestBench for failed integration tests # Error screenshots generated by TestBench for failed integration tests
error-screenshots/ error-screenshots/
webpack.generated.js webpack.generated.js
*.env

1498
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,22 +6,22 @@
"@f0rce/ace-widget": "1.0.2", "@f0rce/ace-widget": "1.0.2",
"@polymer/polymer": "3.5.1", "@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.4.2", "@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.4.2", "@vaadin/polymer-legacy-adapter": "24.5.1",
"@vaadin/react-components": "24.4.2", "@vaadin/react-components": "24.5.1",
"@vaadin/react-components-pro": "24.4.2", "@vaadin/react-components-pro": "24.5.1",
"@vaadin/router": "1.7.5", "@vaadin/router": "2.0.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7", "@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.4.2", "@vaadin/vaadin-lumo-styles": "24.5.1",
"@vaadin/vaadin-material-styles": "24.4.2", "@vaadin/vaadin-material-styles": "24.5.1",
"@vaadin/vaadin-themable-mixin": "24.4.2", "@vaadin/vaadin-themable-mixin": "24.5.1",
"@vaadin/vaadin-usage-statistics": "2.1.2", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.1.4", "lit": "3.1.4",
"print-js": "1.6.0", "print-js": "1.6.0",
"proj4": "2.11.0", "proj4": "2.12.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.23.1" "react-router-dom": "6.23.1"
@ -51,22 +51,22 @@
"@f0rce/ace-widget": "1.0.2", "@f0rce/ace-widget": "1.0.2",
"@polymer/polymer": "3.5.1", "@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.4.2", "@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.4.2", "@vaadin/polymer-legacy-adapter": "24.5.1",
"@vaadin/react-components": "24.4.2", "@vaadin/react-components": "24.5.1",
"@vaadin/react-components-pro": "24.4.2", "@vaadin/react-components-pro": "24.5.1",
"@vaadin/router": "1.7.5", "@vaadin/router": "2.0.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7", "@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.4.2", "@vaadin/vaadin-lumo-styles": "24.5.1",
"@vaadin/vaadin-material-styles": "24.4.2", "@vaadin/vaadin-material-styles": "24.5.1",
"@vaadin/vaadin-themable-mixin": "24.4.2", "@vaadin/vaadin-themable-mixin": "24.5.1",
"@vaadin/vaadin-usage-statistics": "2.1.2", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.1.4", "lit": "3.1.4",
"print-js": "1.6.0", "print-js": "1.6.0",
"proj4": "2.11.0", "proj4": "2.12.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.23.1" "react-router-dom": "6.23.1"
@ -91,7 +91,7 @@
"workbox-core": "7.1.0", "workbox-core": "7.1.0",
"workbox-precaching": "7.1.0" "workbox-precaching": "7.1.0"
}, },
"hash": "0962b593830d75a70657cde2e956e8c49b704d39c45bfd150cda9fdac99f1c6e" "hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436"
}, },
"overrides": { "overrides": {
"@vaadin/bundles": "$@vaadin/bundles", "@vaadin/bundles": "$@vaadin/bundles",

26
pom.xml
View File

@ -11,7 +11,8 @@
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<vaadin.version>24.4.6</vaadin.version> <vaadin.version>24.5.1</vaadin.version>
<vaadin-maven-plugin.version>24.4.6</vaadin-maven-plugin.version>
</properties> </properties>
<parent> <parent>
@ -119,6 +120,10 @@
<artifactId>commons-beanutils</artifactId> <artifactId>commons-beanutils</artifactId>
<version>1.9.4</version> <version>1.9.4</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
@ -181,6 +186,16 @@
<artifactId>viritin</artifactId> <artifactId>viritin</artifactId>
<version>2.8.22</version> <version>2.8.22</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency> <dependency>
<groupId>com.flowingcode.addons</groupId> <groupId>com.flowingcode.addons</groupId>
<artifactId>simple-timer</artifactId> <artifactId>simple-timer</artifactId>
@ -240,6 +255,11 @@
<artifactId>freemarker</artifactId> <artifactId>freemarker</artifactId>
<version>2.3.32</version> <version>2.3.32</version>
</dependency> </dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -279,7 +299,7 @@
<plugin> <plugin>
<groupId>com.vaadin</groupId> <groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId> <artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version> <version>${vaadin-maven-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
@ -340,7 +360,7 @@
<plugin> <plugin>
<groupId>com.vaadin</groupId> <groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId> <artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version> <version>${vaadin-maven-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>

Binary file not shown.

View File

@ -0,0 +1,111 @@
package com.primefactorsolutions.model;
public final class Actividad {
private String nombre;
private double lunes;
private double martes;
private double miercoles;
private double jueves;
private double viernes;
private double sabado;
private double domingo;
public Actividad(final Builder builder) {
this.nombre = builder.nombre;
this.lunes = builder.lunes;
this.martes = builder.martes;
this.miercoles = builder.miercoles;
this.jueves = builder.jueves;
this.viernes = builder.viernes;
this.sabado = builder.sabado;
this.domingo = builder.domingo;
}
public String getNombre() {
return nombre;
}
public double getLunes() {
return lunes;
}
public double getMartes() {
return martes;
}
public double getMiercoles() {
return miercoles;
}
public double getJueves() {
return jueves;
}
public double getViernes() {
return viernes;
}
public double getSabado() {
return sabado;
}
public double getDomingo() {
return domingo;
}
// Builder para crear instancias de Actividad
public static class Builder {
private String nombre;
private double lunes;
private double martes;
private double miercoles;
private double jueves;
private double viernes;
private double sabado;
private double domingo;
public Builder nombre(final String nombre) {
this.nombre = nombre;
return this;
}
public Builder lunes(final double horas) {
this.lunes = horas;
return this;
}
public Builder martes(final double horas) {
this.martes = horas;
return this;
}
public Builder miercoles(final double horas) {
this.miercoles = horas;
return this;
}
public Builder jueves(final double horas) {
this.jueves = horas;
return this;
}
public Builder viernes(final double horas) {
this.viernes = horas;
return this;
}
public Builder sabado(final double horas) {
this.sabado = horas;
return this;
}
public Builder domingo(final double horas) {
this.domingo = horas;
return this;
}
public Actividad build() {
return new Actividad(this);
}
}
}

View File

@ -0,0 +1,56 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import java.util.UUID;
@Entity
public class HoursWorked extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@ManyToOne
private Employee employee;
private int weekNumber;
private double totalHours;
public HoursWorked() { }
public UUID getId() {
return id;
}
public void setId(final UUID id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(final Employee value) {
this.employee = value;
}
public int getWeekNumber() {
return weekNumber;
}
public void setWeekNumber(final int weekNumber) {
this.weekNumber = weekNumber;
}
public double getTotalHours() {
return totalHours;
}
public void setTotalHours(final double totalHours) {
this.totalHours = totalHours;
}
}

View File

@ -8,4 +8,6 @@ import java.util.UUID;
public interface EmployeeRepository extends JpaRepository<Employee, UUID> { public interface EmployeeRepository extends JpaRepository<Employee, UUID> {
Optional<Employee> findByUsername(String username); Optional<Employee> findByUsername(String username);
Optional<Employee> findByPersonalEmail(String personalEmail);
} }

View File

@ -0,0 +1,10 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.HoursWorked;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface HoursWorkedRepository extends JpaRepository<HoursWorked, Long> {
// Puedes definir consultas personalizadas aquí si es necesario.
}

View File

@ -0,0 +1,101 @@
package com.primefactorsolutions.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.primefactorsolutions.model.Employee;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@Service
@Slf4j
public class AccountService {
private final EmailService emailService;
private final EmployeeService employeeService;
private final String secret;
public AccountService(final EmailService emailService, final EmployeeService employeeService,
@Value("${application.jwtSecret}") final String secret) {
this.emailService = emailService;
this.employeeService = employeeService;
this.secret = secret;
}
public void sendResetPasswordEmail(final String personalEmail) {
final Employee employee = employeeService.getEmployeeByPersonalEmail(personalEmail);
if (employee == null) {
log.warn("Could not find employee for email {}", personalEmail);
return;
}
final String link = createResetPasswordLink(employee.getUsername());
final String content = "Visit this link to reset your password: " + link;
emailService.sendEmail(personalEmail, "PFS - Reset Password", content);
}
public void resetPassword(final String username, final String newPassword, final String token) {
DecodedJWT decodedJWT;
try {
Algorithm algorithm = Algorithm.HMAC512(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("pfs")
.build();
decodedJWT = verifier.verify(token);
final Instant expiry = decodedJWT.getExpiresAtAsInstant();
final String claim = decodedJWT.getClaim("username").asString();
if (expiry.isBefore(Instant.now())
|| !username.equals(claim)) {
log.warn("token invalid {} {} {}", username, claim, expiry);
return;
}
} catch (JWTVerificationException e) {
log.warn("error updating password", e);
return;
}
final Employee employee = employeeService.getDetachedEmployeeByUsername(username);
if (employee == null) {
log.warn("Could not find employee for username {}", username);
return;
}
if (StringUtils.isBlank(newPassword) || newPassword.length() < 8) {
throw new IllegalArgumentException("New password should be at least 8 chars long");
}
employeeService.updatePassword(employee, newPassword);
log.info("updated password for {}", username);
}
private String createResetPasswordLink(final String username) {
String token = "";
try {
Algorithm algorithm = Algorithm.HMAC512(secret);
token = JWT.create()
.withIssuer("pfs")
.withClaim("username", username)
.withExpiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
.sign(algorithm);
} catch (JWTCreationException e) {
throw new RuntimeException(e);
}
return String.format("https://intra.primefactorsolutions.com/reset-password?username=%s&token=%s", username,
token);
}
}

View File

@ -0,0 +1,32 @@
package com.primefactorsolutions.service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
@Slf4j
public class EmailService {
public static final String NO_REPLY_PRIMEFACTORSOLUTIONS_COM = "no-reply@primefactorsolutions.com";
private final JavaMailSender emailSender;
public void sendEmail(final String email, final String title, final String messageContent) {
try {
final SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(NO_REPLY_PRIMEFACTORSOLUTIONS_COM);
message.setBcc(NO_REPLY_PRIMEFACTORSOLUTIONS_COM);
message.setTo(email);
message.setSubject(title);
message.setText(messageContent);
emailSender.send(message);
log.info("Sent email to {}", email);
} catch (Exception e) {
log.error("Error sending email to {}", email, e);
throw e;
}
}
}

View File

@ -18,12 +18,18 @@ import java.util.Collections;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
public class EmployeeService { public class EmployeeService {
private static final String USERPASSWORD = "userPassword";
private static final String OBJECTCLASS = "objectclass";
private static final String ORGANIZATIONAL_PERSON = "organizationalPerson";
private static final String INET_ORG_PERSON = "inetOrgPerson";
private static final String TOP = "top";
private static final String PERSON = "person";
public static final String BASE_DN = "dc=primefactorsolutions,dc=com";
private final EmployeeRepository employeeRepository; private final EmployeeRepository employeeRepository;
private final LdapTemplate ldapTemplate; private final LdapTemplate ldapTemplate;
private final EntityManager entityManager; private final EntityManager entityManager;
public static final String BASE_DN = "dc=primefactorsolutions,dc=com";
protected Name buildDn(final Employee employee) { protected Name buildDn(final Employee employee) {
return LdapNameBuilder.newInstance(BASE_DN) return LdapNameBuilder.newInstance(BASE_DN)
.add("ou", "users") .add("ou", "users")
@ -63,6 +69,10 @@ public class EmployeeService {
return employees.subList(start, end); return employees.subList(start, end);
} }
public Employee getEmployeeByPersonalEmail(final String email) {
return employeeRepository.findByPersonalEmail(email).orElse(null);
}
public Employee createOrUpdate(final Employee employee) { public Employee createOrUpdate(final Employee employee) {
if (employee.getId() == null) { if (employee.getId() == null) {
final Name dn = buildDn(employee); final Name dn = buildDn(employee);
@ -74,28 +84,29 @@ public class EmployeeService {
} }
public Employee getEmployee(final UUID id) { public Employee getEmployee(final UUID id) {
Optional<Employee> employee = employeeRepository.findById(id); final Optional<Employee> employee = employeeRepository.findById(id);
return employee.orElse(null); return employee.orElse(null);
} }
private Attributes buildAttributes(final Employee employee) { private Attributes buildAttributes(final Employee employee) {
final Attributes attrs = new BasicAttributes(); final Attributes attrs = new BasicAttributes();
final BasicAttribute ocattr = new BasicAttribute("objectclass"); final BasicAttribute ocattr = new BasicAttribute(OBJECTCLASS);
ocattr.add("top"); ocattr.add(TOP);
ocattr.add("person"); ocattr.add(PERSON);
ocattr.add("organizationalPerson"); ocattr.add(ORGANIZATIONAL_PERSON);
ocattr.add("inetOrgPerson"); ocattr.add(INET_ORG_PERSON);
attrs.put(ocattr); attrs.put(ocattr);
attrs.put("cn", String.format("%s %s", employee.getFirstName(), employee.getLastName())); attrs.put("cn", String.format("%s %s", employee.getFirstName(), employee.getLastName()));
attrs.put("sn", String.format("%s %s", employee.getFirstName(), employee.getLastName())); attrs.put("sn", String.format("%s %s", employee.getFirstName(), employee.getLastName()));
attrs.put("uid", employee.getUsername()); attrs.put("uid", employee.getUsername());
attrs.put("userpassword", String.format("%s%s", employee.getUsername(), 123)); attrs.put(USERPASSWORD, String.format("%s%s", employee.getUsername(), 123));
return attrs; return attrs;
} }
public void updatePassword(final Employee employee) { public void updatePassword(final Employee employee, final String newPassword) {
final Attribute attr = new BasicAttribute("userpassword", employee.getUsername() + "123"); final Attribute attr = new BasicAttribute(USERPASSWORD, newPassword);
final ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); final ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
ldapTemplate.modifyAttributes(buildDn(employee), new ModificationItem[] {item}); ldapTemplate.modifyAttributes(buildDn(employee), new ModificationItem[] {item});

View File

@ -0,0 +1,35 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HoursWorkedService {
private final HoursWorkedRepository hoursWorkedRepository;
@Autowired
public HoursWorkedService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
public List<HoursWorked> findAll() {
return hoursWorkedRepository.findAll();
}
public HoursWorked saveHoursWorked(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public HoursWorked save(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public void deleteHoursWorked(final Long id) {
hoursWorkedRepository.deleteById(id);
}
}

View File

@ -2,6 +2,7 @@ package com.primefactorsolutions.service;
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer; import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper; import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template; import freemarker.template.Template;
@ -9,15 +10,77 @@ import freemarker.template.TemplateExceptionHandler;
import lombok.SneakyThrows; 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.xssf.usermodel.XSSFWorkbook;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
@Service @Service
public class ReportService { public class ReportService {
private final HoursWorkedRepository hoursWorkedRepository;
public ReportService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
// 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,
final List<Map<String, Object>> data)
throws IOException {
return createExcelFile(reportName, headers, data);
}
private byte[] createExcelFile(final String reportName, final List<String> headers,
final List<Map<String, Object>> data)
throws IOException {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet(reportName);
// Crear encabezados
Row headerRow = sheet.createRow(0);
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
for (int i = 0; i < headers.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers.get(i));
cell.setCellStyle(headerStyle);
}
// Crear filas de datos
for (int i = 0; i < data.size(); i++) {
Row dataRow = sheet.createRow(i + 1);
Map<String, Object> rowData = data.get(i);
int cellIndex = 0;
for (String key : headers) {
Cell cell = dataRow.createCell(cellIndex++);
Object value = rowData.get(key);
switch (value) {
case String s -> cell.setCellValue(s);
case Number number -> cell.setCellValue(number.doubleValue());
case null -> cell.setCellValue(""); // Manejo de valores nulos
default -> {
}
}
}
}
workbook.write(os);
return os.toByteArray();
} catch (IOException e) {
System.err.println("Error al generar el archivo Excel: " + e.getMessage());
throw e; // Propagar la excepción después de registrarla
}
}
@SneakyThrows @SneakyThrows
public byte[] writeAsPdf(final String reportName, final Object model) { public byte[] writeAsPdf(final String reportName, final Object model) {

View File

@ -158,6 +158,18 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
configureComponents(); configureComponents();
addClassName("main-layout"); addClassName("main-layout");
getBinder().setConverter("team", new Converter<Object, Object>() {
@Override
public Result<Object> convertToModel(final Object o, final ValueContext valueContext) {
return Result.ok(new Team((String) o));
}
@Override
public Object convertToPresentation(final Object o, final ValueContext valueContext) {
return ((Team) o).getName();
}
});
} }
private void configureComponents() { private void configureComponents() {

View File

@ -196,7 +196,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
final MenuBar navMenuBar = new MenuBar(); final MenuBar navMenuBar = new MenuBar();
prev = navMenuBar.addItem("Anterior pregunta", prev = navMenuBar.addItem("Anterior pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
log.info(">>> prev");
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission); this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission);
@ -253,7 +252,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
start = new Button("Empezar"); start = new Button("Empezar");
start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> { start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
log.info(">>> start");
this.assessment = this.assessmentService.startAssessment(this.assessment.getId()); this.assessment = this.assessmentService.startAssessment(this.assessment.getId());
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) { if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) {
@ -318,7 +316,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
} }
private void goToNext() { private void goToNext() {
log.info(">>> next");
Submission found = this.assessmentService.getNextSubmission(assessment.getId(), Submission found = this.assessmentService.getNextSubmission(assessment.getId(),
this.currSubmission.getId()); this.currSubmission.getId());

View File

@ -0,0 +1,161 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.Actividad;
import com.primefactorsolutions.service.EmployeeService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Label;
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.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import java.time.LocalDate;
import java.util.List;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Hours Worked Month")
@Route(value = "/hours-worked-month/me", layout = MainLayout.class)
public class HoursWorkedMonthView extends VerticalLayout {
private final EmployeeService employeeService;
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Empleado");
private final ComboBox<String> equipoDropdown = new ComboBox<>("Equipo");
private final Grid<Actividad> grid = new Grid<>(Actividad.class);
private final Label totalCompletadoLabel = new Label();
private final Label horasPendientesLabel = new Label();
private final Label totalAcumuladasLabel = new Label();
private final Label horasAdeudadasLabel = new Label();
private final Button actualizarButton = new Button("Actualizar");
private final Button guardarButton = new Button("Guardar");
private final Button cerrarButton = new Button("Cerrar");
private LocalDate selectedMonth;
@Autowired
public HoursWorkedMonthView(final EmployeeService employeeService) {
this.employeeService = employeeService;
configurarVista();
}
private void configurarVista() {
DatePicker monthPicker = new DatePicker("Selecciona un mes");
monthPicker.setValue(LocalDate.now());
monthPicker.addValueChangeListener(event -> {
selectedMonth = event.getValue().withDayOfMonth(1);
//cargarDatosMes(selectedMonth);
});
equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3");
equipoDropdown.setWidth("250px");
setEmployeeComboBoxProperties();
configurarGrid();
actualizarButton.addClickListener(event -> actualizarDatos());
guardarButton.addClickListener(event -> guardarActividades());
cerrarButton.addClickListener(event -> closeView());
HorizontalLayout headerLayout = new HorizontalLayout(monthPicker, equipoDropdown, employeeComboBox);
add(
headerLayout, grid, totalCompletadoLabel,
horasPendientesLabel, totalAcumuladasLabel,
horasAdeudadasLabel, actualizarButton,
guardarButton, cerrarButton);
}
private void setEmployeeComboBoxProperties() {
employeeComboBox.setWidth("250px");
employeeComboBox.setPlaceholder("Buscar empleado...");
employeeComboBox.setItems(employeeService.findAllEmployees());
employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName());
}
private void configurarGrid() {
grid.removeAllColumns();
grid.addColumn(Actividad::getLunes).setHeader("Lunes");
grid.addColumn(Actividad::getMartes).setHeader("Martes");
grid.addColumn(Actividad::getMiercoles).setHeader("Miércoles");
grid.addColumn(Actividad::getJueves).setHeader("Jueves");
grid.addColumn(Actividad::getViernes).setHeader("Viernes");
grid.addColumn(Actividad::getSabado).setHeader("Sábado");
grid.addColumn(Actividad::getDomingo).setHeader("Domingo");
grid.addColumn(this::calcularTotalPorDia).setHeader("Total Semanal").setKey("totalSemanal");
}
// private void cargarDatosMes(final LocalDate month) {
// List<Actividad> actividadesDelMes = obtenerActividadesDelMes(month);
// grid.setItems(actividadesDelMes);
//
// double totalCompletado = calcularTotalCompletado(actividadesDelMes);
// double horasPendientes = calcularHorasPendientes(totalCompletado);
// double totalAcumuladas = 166;
// double horasAdeudadas = 2;
//
// totalCompletadoLabel.setText("Prom. Hrs/Semana Completadas: " + totalCompletado);
// horasPendientesLabel.setText("Prom. Hrs/Semana Pendientes: " + horasPendientes);
// totalAcumuladasLabel.setText("Total Hrs./Mes Acumuladas: " + totalAcumuladas);
// horasAdeudadasLabel.setText("Total Hrs./Mes Adeudadas: " + horasAdeudadas);
// }
// private List<Actividad> obtenerActividadesDelMes(final LocalDate month) {
// LocalDate startOfMonth = month.with(TemporalAdjusters.firstDayOfMonth());
// LocalDate endOfMonth = month.with(TemporalAdjusters.lastDayOfMonth());
//
// List<Actividad> actividadesDelMes = new ArrayList<>();
//
// for (LocalDate date = startOfMonth; date.isBefore(endOfMonth.plusDays(1)); date = date.plusDays(1)) {
// Actividad actividad = new Actividad.Builder()
// .lunes(0)
// .martes(0)
// .miercoles(0)
// .jueves(0)
// .viernes(0)
// .sabado(0)
// .domingo(0)
// .build();
// actividadesDelMes.add(actividad);
// }
//
// return actividadesDelMes;
// }
private double calcularTotalCompletado(final List<Actividad> actividades) {
return actividades.stream()
.mapToDouble(this::calcularTotalPorDia)
.sum();
}
private double calcularTotalPorDia(final Actividad actividad) {
return actividad.getLunes() + actividad.getMartes() + actividad.getMiercoles()
+ actividad.getJueves() + actividad.getViernes() + actividad.getSabado()
+ actividad.getDomingo();
}
private double calcularHorasPendientes(final double totalCompletado) {
return 40 - totalCompletado;
}
private void actualizarDatos() {
Notification.show("Datos actualizados.");
}
private void guardarActividades() {
Notification.show("Actividades guardadas correctamente.");
}
private void closeView() {
getUI().ifPresent(ui -> ui.navigate(""));
}
}

View File

@ -1,19 +1,33 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Actividad;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.HoursWorkedService;
import com.vaadin.flow.component.datepicker.DatePicker; import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
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.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.html.H2;
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.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.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.html.Label;
import java.time.DayOfWeek;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.IsoFields;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@SpringComponent @SpringComponent
@PermitAll @PermitAll
@ -21,20 +35,107 @@ import java.time.LocalDate;
@PageTitle("Hours Worked") @PageTitle("Hours Worked")
@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 {
public HoursWorkedView() { private final List<Actividad> actividades = new ArrayList<>();
H2 title = new H2("Registro de Horas Trabajadas"); private final Grid<Actividad> grid = new Grid<>(Actividad.class);
DatePicker datePicker = new DatePicker("Selecciona una fecha"); private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Employee");
datePicker.setValue(LocalDate.now()); private LocalDate selectedStartOfWeek;
private int weekNumber;
@Autowired
private final EmployeeService employeeService;
private final Label fechasLabel = new Label("Selecciona una semana para ver las fechas.");
private final Label totalCompletadoLabel = new Label();
private final Label horasPendientesLabel = new Label();
@Autowired
private final HoursWorkedService hoursWorkedService;
public HoursWorkedView(final EmployeeService employeeService, final HoursWorkedService hoursWorkedService) {
this.employeeService = employeeService;
this.hoursWorkedService = hoursWorkedService;
configurarVista();
cargarDatos();
}
private void cargarDatos() {
List<HoursWorked> listaDeHorasTrabajadas = obtenerDatos(); // Obtenemos la lista aquí
grid.setItems(actividades);
double totalHoras = calcularTotalHoras(listaDeHorasTrabajadas); // Pasa la lista aquí
}
private void setEmployeeComboBoxProperties() {
employeeComboBox.setWidth("250px");
employeeComboBox.setPlaceholder("Buscar empleado...");
employeeComboBox.setItems(employeeService.findAllEmployees());
employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName());
employeeComboBox.setAllowCustomValue(false);
employeeComboBox.addCustomValueSetListener(event ->
Notification.show("Selecciona un empleado válido de la lista.")
);
employeeComboBox.addValueChangeListener(event -> {
Employee selectedEmployee = event.getValue();
if (selectedEmployee != null) {
Notification.show("Empleado seleccionado: "
+ selectedEmployee.getFirstName() + " "
+ selectedEmployee.getLastName());
}
});
}
private int getWeekOfYear(final LocalDate date) {
return date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
}
private void configurarVista() {
DatePicker fechaPicker = new DatePicker("Selecciona una fecha");
fechaPicker.addValueChangeListener(event -> {
LocalDate selectedDate = event.getValue();
if (selectedDate != null) {
selectedStartOfWeek = getStartOfWeek(selectedDate);
LocalDate endOfWeek = selectedStartOfWeek.plusDays(6);
fechasLabel.setText("Semana del " + selectedStartOfWeek + " al " + endOfWeek);
weekNumber = getWeekOfYear(selectedDate);
}
});
Button verMesButton = new Button("Ver Mes", event -> {
getUI().ifPresent(ui -> ui.navigate(HoursWorkedMonthView.class));
});
ComboBox<String> equipoDropdown = new ComboBox<>("Equipo"); ComboBox<String> equipoDropdown = new ComboBox<>("Equipo");
equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3"); // Ejemplo de datos equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3");
equipoDropdown.setWidth("250px");
TextField empleadoSearch = new TextField("Empleado (Search)"); setEmployeeComboBoxProperties();
HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, empleadoSearch); HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, employeeComboBox);
filtersLayout.setSpacing(true);
Grid<Actividad> grid = new Grid<>(Actividad.class, false); configurarGrid();
HorizontalLayout actividadFormLayout = configurarFormularioActividades();
Button actualizarButton = new Button("Actualizar Totales", event -> actualizarTotales());
Button guardarButton = new Button("Guardar", event -> guardarActividades());
Button cerrarButton = new Button("Cerrar", event -> this.closeView());
HorizontalLayout buttonsLayout = new HorizontalLayout(actualizarButton, guardarButton,
cerrarButton, verMesButton);
VerticalLayout totalesLayout = new VerticalLayout(totalCompletadoLabel, horasPendientesLabel);
totalesLayout.setSpacing(true);
totalesLayout.setPadding(true);
add(fechaPicker, fechasLabel, filtersLayout, grid, actividadFormLayout, buttonsLayout, totalesLayout);
}
private void configurarGrid() {
grid.removeAllColumns();
grid.setItems(actividades);
grid.addColumn(Actividad::getNombre).setHeader("Actividad"); grid.addColumn(Actividad::getNombre).setHeader("Actividad");
grid.addColumn(Actividad::getLunes).setHeader("Lunes"); grid.addColumn(Actividad::getLunes).setHeader("Lunes");
grid.addColumn(Actividad::getMartes).setHeader("Martes"); grid.addColumn(Actividad::getMartes).setHeader("Martes");
@ -43,155 +144,127 @@ public class HoursWorkedView extends VerticalLayout {
grid.addColumn(Actividad::getViernes).setHeader("Viernes"); grid.addColumn(Actividad::getViernes).setHeader("Viernes");
grid.addColumn(Actividad::getSabado).setHeader("Sábado"); grid.addColumn(Actividad::getSabado).setHeader("Sábado");
grid.addColumn(Actividad::getDomingo).setHeader("Domingo"); grid.addColumn(Actividad::getDomingo).setHeader("Domingo");
grid.addColumn(this::calcularTotalPorDia).setHeader("Total Día").setKey("totalDia");
grid.setItems(
new Actividad.Builder()
.nombre("Actividad 1")
.lunes(3)
.martes(3)
.miercoles(3)
.jueves(3)
.viernes(3)
.sabado(1)
.domingo(2)
.build(),
new Actividad.Builder()
.nombre("Actividad 2")
.lunes(2)
.martes(2)
.miercoles(2)
.jueves(2)
.viernes(2)
.sabado(0)
.domingo(1)
.build(),
new Actividad.Builder()
.nombre("Meeting 1")
.lunes(0)
.martes(0.5)
.miercoles(0.5)
.jueves(0)
.viernes(0)
.sabado(0.5)
.domingo(0)
.build()
);
Button actualizarButton = new Button("Actualizar");
Button guardarButton = new Button("Guardar");
Button cerrarButton = new Button("Cerrar");
HorizontalLayout buttonsLayout = new HorizontalLayout(actualizarButton, guardarButton, cerrarButton);
add(title, datePicker, filtersLayout, grid, buttonsLayout);
} }
public static final class Actividad { private HorizontalLayout configurarFormularioActividades() {
private final String nombre; TextField actividadNombre = new TextField("Actividad");
private final double lunes; actividadNombre.setWidth("200px");
private final double martes;
private final double miercoles;
private final double jueves;
private final double viernes;
private final double sabado;
private final double domingo;
private Actividad(final Builder builder) { TextField lunesHoras = crearCampoHora("Lunes");
this.nombre = builder.nombre; TextField martesHoras = crearCampoHora("Martes");
this.lunes = builder.lunes; TextField miercolesHoras = crearCampoHora("Miércoles");
this.martes = builder.martes; TextField juevesHoras = crearCampoHora("Jueves");
this.miercoles = builder.miercoles; TextField viernesHoras = crearCampoHora("Viernes");
this.jueves = builder.jueves; TextField sabadoHoras = crearCampoHora("Sábado");
this.viernes = builder.viernes; TextField domingoHoras = crearCampoHora("Domingo");
this.sabado = builder.sabado;
this.domingo = builder.domingo; Button agregarActividadButton = new Button("Agregar Actividad", e -> {
try {
Actividad nuevaActividad = new Actividad.Builder()
.nombre(actividadNombre.getValue())
.lunes(parseHoras(lunesHoras.getValue()))
.martes(parseHoras(martesHoras.getValue()))
.miercoles(parseHoras(miercolesHoras.getValue()))
.jueves(parseHoras(juevesHoras.getValue()))
.viernes(parseHoras(viernesHoras.getValue()))
.sabado(parseHoras(sabadoHoras.getValue()))
.domingo(parseHoras(domingoHoras.getValue()))
.build();
actividades.add(nuevaActividad);
grid.setItems(actividades);
actualizarTotales();
Notification.show("Actividad agregada correctamente");
// Limpiar los campos de entrada
actividadNombre.clear();
lunesHoras.clear();
martesHoras.clear();
miercolesHoras.clear();
juevesHoras.clear();
viernesHoras.clear();
sabadoHoras.clear();
domingoHoras.clear();
} catch (NumberFormatException ex) {
Notification.show("Error: Por favor ingresa números válidos para las horas.");
}
});
return new HorizontalLayout(
actividadNombre, lunesHoras, martesHoras, miercolesHoras,
juevesHoras, viernesHoras, sabadoHoras, domingoHoras, agregarActividadButton);
} }
public static class Builder { private TextField crearCampoHora(final String placeholder) {
private String nombre; TextField field = new TextField(placeholder);
private double lunes; field.setWidth("80px");
private double martes; field.setPlaceholder("0.0");
private double miercoles; return field;
private double jueves;
private double viernes;
private double sabado;
private double domingo;
public Builder nombre(final String nombre) {
this.nombre = nombre;
return this;
} }
public Builder lunes(final double lunes) { private double parseHoras(final String value) {
this.lunes = lunes; if (value == null || value.trim().isEmpty()) {
return this; return 0.0;
}
return Double.parseDouble(value);
} }
public Builder martes(final double martes) { private LocalDate getStartOfWeek(final LocalDate date) {
this.martes = martes; WeekFields weekFields = WeekFields.of(Locale.getDefault());
return this; return date.with(weekFields.dayOfWeek(), DayOfWeek.MONDAY.getValue());
} }
public Builder miercoles(final double miercoles) { private double calcularTotalPorDia(final Actividad actividad) {
this.miercoles = miercoles; return actividad.getLunes() + actividad.getMartes() + actividad.getMiercoles()
return this; + actividad.getJueves() + actividad.getViernes() + actividad.getSabado() + actividad.getDomingo();
} }
public Builder jueves(final double jueves) { private void actualizarTotales() {
this.jueves = jueves; double totalSemanaCompletada = actividades.stream()
return this; .mapToDouble(this::calcularTotalPorDia)
.sum();
double horasPendientes = 40 - totalSemanaCompletada;
totalCompletadoLabel.setText("Total Hrs/Semana Completadas: " + totalSemanaCompletada);
horasPendientesLabel.setText("Horas Pendientes: " + horasPendientes);
} }
public Builder viernes(final double viernes) { private void guardarActividades() {
this.viernes = viernes; Employee selectedEmployee = employeeComboBox.getValue();
return this;
if (selectedEmployee == null) {
Notification.show("Por favor, selecciona un empleado antes de guardar.");
return;
} }
public Builder sabado(final double sabado) { double totalHorasSemana = actividades.stream()
this.sabado = sabado; .mapToDouble(this::calcularTotalPorDia)
return this; .sum();
}
public Builder domingo(final double domingo) { HoursWorked hoursWorked = new HoursWorked();
this.domingo = domingo; hoursWorked.setEmployee(selectedEmployee);
return this; hoursWorked.setWeekNumber(weekNumber);
} hoursWorked.setTotalHours(totalHorasSemana);
public Actividad build() { try {
return new Actividad(this); hoursWorkedService.saveHoursWorked(hoursWorked); // Usa saveHoursWorked directamente
Notification.show("Actividades guardadas correctamente.");
} catch (Exception e) {
Notification.show("Error al guardar actividades: " + e.getMessage());
} }
} }
public String getNombre() { private double calcularTotalHoras(final List<HoursWorked> listaDeHorasTrabajadas) {
return nombre; return listaDeHorasTrabajadas.stream()
.mapToDouble(HoursWorked::getTotalHours)
.sum();
} }
public double getLunes() { private List<HoursWorked> obtenerDatos() {
return lunes; return new ArrayList<>();
} }
public double getMartes() { private void closeView() {
return martes; getUI().ifPresent(ui -> ui.navigate(HoursWorkedView.class));
}
public double getMiercoles() {
return miercoles;
}
public double getJueves() {
return jueves;
}
public double getViernes() {
return viernes;
}
public double getSabado() {
return sabado;
}
public double getDomingo() {
return domingo;
}
} }
} }

View File

@ -0,0 +1,53 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
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;
@Route("init-account")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class InitAccountView extends VerticalLayout implements BeforeEnterObserver {
public InitAccountView() {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final PasswordField password = new PasswordField("Password");
final PasswordField confirmPassword = new PasswordField("Confirm Password");
final FormLayout formLayout = new FormLayout(password, confirmPassword);
formLayout.setColspan(password, 3);
formLayout.setColspan(confirmPassword, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final Button secondaryButton = new Button("Cancel");
HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
vl.add(new H3("Set Account Password"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
}
}

View File

@ -1,5 +1,6 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.login.LoginForm; import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
@ -23,9 +24,11 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver {
setJustifyContentMode(JustifyContentMode.CENTER); setJustifyContentMode(JustifyContentMode.CENTER);
login.setAction("login"); login.setAction("login");
login.setForgotPasswordButtonVisible(false);
add(new H1("PFS Intra")); add(new H1("PFS Intra"));
add(login); add(login);
add(new Anchor("/password-recovery", "Reset password?"));
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.model.Employee;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text; import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.applayout.DrawerToggle;
@ -16,7 +17,6 @@ import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.Scroller; import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.shared.Tooltip;
import com.vaadin.flow.component.sidenav.SideNav; import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem; import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
@ -51,18 +51,6 @@ public class MainLayout extends AppLayout {
final HorizontalLayout header = authContext.getAuthenticatedUser(UserDetails.class) final HorizontalLayout header = authContext.getAuthenticatedUser(UserDetails.class)
.map(user -> { .map(user -> {
final Avatar loggedUser = new Avatar(user.getUsername());
loggedUser.getStyle().set("display", "block");
loggedUser.getElement().setAttribute("tabindex", "-1");
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_ICON);
final MenuItem actions = createIconItem(menuBar, VaadinIcon.ELLIPSIS_V, null, "",
false);
final SubMenu actionsSubMenu = actions.getSubMenu();
final MenuItem signOutMenuItem = createIconItem(actionsSubMenu, VaadinIcon.EXIT, "Sign-out",
null, true);
signOutMenuItem.addClickListener(c -> this.authContext.logout());
String employeeId = "N/A"; String employeeId = "N/A";
if (user instanceof Employee) { if (user instanceof Employee) {
@ -73,11 +61,19 @@ public class MainLayout extends AppLayout {
} }
} }
final Tooltip tooltip = Tooltip.forComponent(loggedUser) final Avatar loggedUser = new Avatar(user.getUsername());
.withText("Employee id: " + employeeId) loggedUser.getStyle().set("display", "block");
.withPosition(Tooltip.TooltipPosition.TOP_START); loggedUser.getElement().setAttribute("tabindex", "-1");
final HorizontalLayout hl = new HorizontalLayout(loggedUser, menuBar); final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem actions = createIconItem(menuBar, loggedUser, null, employeeId);
final SubMenu actionsSubMenu = actions.getSubMenu();
final MenuItem signOutMenuItem = createIconItem(actionsSubMenu,
createIcon(VaadinIcon.EXIT, true), "Sign-out", null);
signOutMenuItem.addClickListener(c -> this.authContext.logout());
final HorizontalLayout hl = new HorizontalLayout(menuBar);
hl.setJustifyContentMode(FlexComponent.JustifyContentMode.END); hl.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
return hl; return hl;
@ -88,17 +84,9 @@ public class MainLayout extends AppLayout {
addToNavbar(true, toggle, viewTitle, header); addToNavbar(true, toggle, viewTitle, header);
} }
private MenuItem createIconItem(final HasMenuItems menu, final VaadinIcon iconName, private MenuItem createIconItem(final HasMenuItems menu, final Component component,
final String label, final String ariaLabel, final boolean isChild) { final String label, final String ariaLabel) {
final Icon icon = new Icon(iconName); final MenuItem item = menu.addItem(component, e -> {
if (isChild) {
icon.getStyle().set("width", "var(--lumo-icon-size-s)");
icon.getStyle().set("height", "var(--lumo-icon-size-s)");
icon.getStyle().set("marginRight", "var(--lumo-space-s)");
}
final MenuItem item = menu.addItem(icon, e -> {
}); });
if (ariaLabel != null) { if (ariaLabel != null) {
@ -112,6 +100,18 @@ public class MainLayout extends AppLayout {
return item; return item;
} }
private Icon createIcon(final VaadinIcon iconName, final boolean isChild) {
final Icon icon = new Icon(iconName);
if (isChild) {
icon.getStyle().set("width", "var(--lumo-icon-size-s)");
icon.getStyle().set("height", "var(--lumo-icon-size-s)");
icon.getStyle().set("marginRight", "var(--lumo-space-s)");
}
return icon;
}
private void addDrawerContent() { private void addDrawerContent() {
final Span appName = new Span("pfs-intra"); final Span appName = new Span("pfs-intra");
appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE); appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE);
@ -124,8 +124,8 @@ public class MainLayout extends AppLayout {
final SideNav nav = new SideNav(); final SideNav nav = new SideNav();
authContext.getAuthenticatedUser(UserDetails.class).ifPresent(u -> { authContext.getAuthenticatedUser(UserDetails.class).ifPresent(u -> {
SideNavItem recruiting = new SideNavItem("Recruiting", MainView.class, SideNavItem recruiting = new SideNavItem("Recruiting");
LineAwesomeIcon.BUSINESS_TIME_SOLID.create()); recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create());
recruiting.addItem(new SideNavItem("Assessments", AssessmentsListView.class, recruiting.addItem(new SideNavItem("Assessments", AssessmentsListView.class,
LineAwesomeIcon.RIBBON_SOLID.create())); LineAwesomeIcon.RIBBON_SOLID.create()));
recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class, recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class,
@ -133,8 +133,8 @@ public class MainLayout extends AppLayout {
recruiting.addItem(new SideNavItem("Questions", QuestionsListView.class, recruiting.addItem(new SideNavItem("Questions", QuestionsListView.class,
LineAwesomeIcon.QUESTION_SOLID.create())); LineAwesomeIcon.QUESTION_SOLID.create()));
SideNavItem admin = new SideNavItem("Admin", MainView.class, SideNavItem admin = new SideNavItem("Admin");
LineAwesomeIcon.BUILDING.create()); admin.setPrefixComponent(LineAwesomeIcon.BUILDING.create());
admin.addItem(new SideNavItem("Employees", EmployeesListView.class, admin.addItem(new SideNavItem("Employees", EmployeesListView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create())); LineAwesomeIcon.USER_EDIT_SOLID.create()));
admin.addItem(new SideNavItem("Documents", DocumentsListView.class, admin.addItem(new SideNavItem("Documents", DocumentsListView.class,

View File

@ -0,0 +1,67 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.service.AccountService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.EmailField;
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;
@Route("password-recovery")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class PasswordRecoveryView extends VerticalLayout implements BeforeEnterObserver {
public PasswordRecoveryView(final AccountService accountService) {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final EmailField personalEmail = new EmailField("Personal Email");
personalEmail.setRequired(true);
final EmailField confirmPersonalEmail = new EmailField("Confirm Personal Email");
confirmPersonalEmail.setRequired(true);
final FormLayout formLayout = new FormLayout(personalEmail, confirmPersonalEmail);
formLayout.setColspan(personalEmail, 3);
formLayout.setColspan(confirmPersonalEmail, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
primaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
if (personalEmail.getValue().equals(confirmPersonalEmail.getValue())) {
accountService.sendResetPasswordEmail(personalEmail.getValue());
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
});
final Button secondaryButton = new Button("Cancel");
final HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
secondaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
getUI().ifPresent(ui -> ui.navigate(MainView.class)));
vl.add(new H3("PFS - Password Recovery"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
}
}

View File

@ -0,0 +1,87 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.ReportService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired;
import com.vaadin.flow.component.notification.Notification;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@PermitAll
@Route(value = "/reportes", layout = MainLayout.class)
@PageTitle("Reporte de Horas Trabajadas")
public class ReporteView extends VerticalLayout {
private final HoursWorkedService hoursWorkedService;
private final ReportService reportService;
@Autowired
public ReporteView(final HoursWorkedService hoursWorkedService, final ReportService reportService) {
this.hoursWorkedService = hoursWorkedService;
this.reportService = reportService;
H2 title = new H2("Reporte de Horas Trabajadas");
add(title);
Button reportButton = new Button("Generar Reporte de Horas Trabajadas", event -> generateHoursWorkedReport());
add(reportButton);
}
private void generateHoursWorkedReport() {
List<HoursWorked> hoursWorkedList = hoursWorkedService.findAll(); // Obtener la lista de HoursWorked
if (hoursWorkedList.isEmpty()) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
try {
List<String> headers = List.of("ID", "Employee ID", "Week Number", "Total Hours");
List<Map<String, Object>> data = hoursWorkedList.stream()
.map(hoursWorked -> {
Map<String, Object> map = new HashMap<>();
map.put("ID", hoursWorked.getId().toString());
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);
StreamResource excelResource = new StreamResource("hours_worked_report.xlsx",
() -> new ByteArrayInputStream(excelBytes));
excelResource.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
excelResource.setCacheTime(0);
Anchor downloadLink = new Anchor(excelResource, "Descargar Reporte de Horas Trabajadas");
downloadLink.getElement().setAttribute("download", true);
add(downloadLink);
Notification.show("Reporte de horas trabajadas generado exitosamente.",
3000, Notification.Position.MIDDLE);
} catch (Exception e) {
Notification.show("Error al generar el reporte de horas trabajadas. Inténtalo de nuevo.",
3000, Notification.Position.MIDDLE);
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,71 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.service.AccountService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import java.util.List;
@Route("reset-password")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class ResetPasswordView extends VerticalLayout implements BeforeEnterObserver {
private String username;
private String token;
public ResetPasswordView(final AccountService accountService) {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final PasswordField password = new PasswordField("Password");
final PasswordField confirmPassword = new PasswordField("Confirm Password");
final FormLayout formLayout = new FormLayout(password, confirmPassword);
formLayout.setColspan(password, 3);
formLayout.setColspan(confirmPassword, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
primaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
accountService.resetPassword(username, password.getValue(), token);
getUI().ifPresent(ui -> ui.navigate(MainView.class));
});
final Button secondaryButton = new Button("Cancel");
secondaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
getUI().ifPresent(ui -> ui.navigate(MainView.class)));
HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
vl.add(new H3("PFS - Reset Password"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
final Location location = beforeEnterEvent.getLocation();
final QueryParameters queryParameters = location.getQueryParameters();
this.username = queryParameters.getParameters().getOrDefault("username", List.of()).stream()
.findFirst().orElse(null);
this.token = queryParameters.getParameters().getOrDefault("token", List.of()).stream()
.findFirst().orElse(null);
}
}

View File

@ -1,2 +1,3 @@
spring.ldap.url=ldap://localhost:8391 spring.ldap.url=ldap://localhost:8391
spring.ldap.embedded.port=8391 spring.ldap.embedded.port=8391
application.jwtSecret=test123

View File

@ -44,3 +44,5 @@ spring.sql.init.mode=${SQL_INIT:embedded}
spring.h2.console.enabled=true spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true spring.h2.console.settings.web-allow-others=true
application.jwtSecret=${JWT_SECRET:changeme}