From 3320a4e99dda36b65bb7aab303e0de7ddb84b197 Mon Sep 17 00:00:00 2001 From: ricardo051199 Date: Wed, 4 Sep 2024 17:53:01 +0000 Subject: [PATCH] Rama-Ricardo (#14) Funcionalidades: Navegacion entre paginas con los botones previous-next, ordenar ascendente-descendente Reviewed-on: https://git.primefactorsolutions.com/PFS/pfs-intra/pulls/14 Reviewed-by: alex Co-authored-by: ricardo051199 Co-committed-by: ricardo051199 --- pom.xml | 5 + .../primefactorsolutions/model/Employee.java | 70 ++++--- .../service/EmployeeService.java | 40 ++-- .../views/EmployeeView.java | 169 +++++++++++++++- .../views/EmployeesListView.java | 185 ++++++++---------- src/main/resources/data.sql | 13 +- 6 files changed, 336 insertions(+), 146 deletions(-) diff --git a/pom.xml b/pom.xml index e192968..cdd0c6d 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,11 @@ spring-boot-starter-test test + + commons-beanutils + commons-beanutils + 1.9.4 + org.mockito mockito-core diff --git a/src/main/java/com/primefactorsolutions/model/Employee.java b/src/main/java/com/primefactorsolutions/model/Employee.java index 6e883f0..c6d918b 100644 --- a/src/main/java/com/primefactorsolutions/model/Employee.java +++ b/src/main/java/com/primefactorsolutions/model/Employee.java @@ -1,29 +1,47 @@ -package com.primefactorsolutions.model; + package com.primefactorsolutions.model; + import jakarta.persistence.*; + import lombok.AllArgsConstructor; + import lombok.Data; + import lombok.EqualsAndHashCode; + import lombok.NoArgsConstructor; -import io.hypersistence.utils.hibernate.type.json.JsonType; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; + import java.time.LocalDate; -import java.time.LocalDate; -import java.util.List; - -@Data -@Entity -@AllArgsConstructor -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class Employee extends BaseEntity { - private String firstName; - private String lastName; - @Enumerated(EnumType.STRING) - private Status status; - - public enum Status { - ACTIVE, - INACTIVE + @Data + @Entity + @AllArgsConstructor + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public class Employee extends BaseEntity { + private String firstName; + private String lastName; + private LocalDate birthday; + private String birthCity; + @Enumerated(EnumType.STRING) + private MaritalStatus maritalStatus; + private String residenceAddress; + private String phoneNumber; + private String personalEmail; + private String emergencyCName; + private String emergencyCAddress; + private String emergencyCPhone; + private String emergencyCEmail; + @Enumerated(EnumType.STRING) + private Status status; + public enum Status { + ACTIVE, + INACTIVE + } + public enum MaritalStatus { + SINGLE, + MARRIED, + WIDOWED, + DIVORCED + } + public Status getStatus() { + return status; + } + public void setStatus(final Status status) { + this.status = status; + } } -} diff --git a/src/main/java/com/primefactorsolutions/service/EmployeeService.java b/src/main/java/com/primefactorsolutions/service/EmployeeService.java index 716827d..993eecd 100644 --- a/src/main/java/com/primefactorsolutions/service/EmployeeService.java +++ b/src/main/java/com/primefactorsolutions/service/EmployeeService.java @@ -1,34 +1,52 @@ package com.primefactorsolutions.service; - -import com.primefactorsolutions.model.Candidate; import com.primefactorsolutions.model.Employee; +import org.apache.commons.beanutils.BeanComparator; import com.primefactorsolutions.repositories.EmployeeRepository; import lombok.Data; -import org.apache.commons.lang3.NotImplementedException; import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; +import java.util.Optional; +import java.util.Collections; + @Service @Data public class EmployeeService { private final EmployeeRepository employeeRepository; - public EmployeeService(EmployeeRepository employeeRepository) { + public EmployeeService(final EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository; } - public Employee createOrUpdate(final Employee employee) { - final Employee saved = employeeRepository.save(employee); - return saved; + public List findEmployees( + final int start, final int pageSize, final String sortProperty, final boolean asc) { + List employees = employeeRepository.findAll(); + + int end = Math.min(start + pageSize, employees.size()); + employees.sort(new BeanComparator<>(sortProperty)); + + if (!asc) { + Collections.reverse(employees); + } + + return employees.subList(start, end); } - public List getEmployees() { - return employeeRepository.findAll(); + public List findEmployees(final int start, final int pageSize) { + List employees = employeeRepository.findAll(); + + int end = Math.min(start + pageSize, employees.size()); + return employees.subList(start, end); + } + + public Employee createOrUpdate(final Employee employee) { + return employeeRepository.save(employee); } public Employee getEmployee(final UUID id) { - return null; + Optional employee = employeeRepository.findById(id); + return employee.orElse(null); } -} +} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/views/EmployeeView.java b/src/main/java/com/primefactorsolutions/views/EmployeeView.java index 6003b76..9e49fe4 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeeView.java @@ -3,8 +3,16 @@ package com.primefactorsolutions.views; import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.service.EmployeeService; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.H3; +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.textfield.EmailField; import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.PageTitle; @@ -13,6 +21,7 @@ import com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Scope; +import org.vaadin.firitin.components.datepicker.VDatePicker; import org.vaadin.firitin.form.BeanValidationForm; import java.util.List; @@ -24,21 +33,150 @@ import java.util.UUID; @PageTitle("Employee") @Route(value = "/employees", layout = MainLayout.class) public class EmployeeView extends BeanValidationForm implements HasUrlParameter { + private final EmployeeService employeeService; - private TextField name = null; + private final TextField firstName; + private final TextField lastName; + private final ComboBox status; + private final VDatePicker birthday; + private final TextField birthCity; + private final ComboBox maritalStatus; + private final TextField residenceAddress; + private final TextField phoneNumber; + private final EmailField personalEmail; + + private final TextField emergencyCName; + private final TextField emergencyCAddress; + private final TextField emergencyCPhone; + private final EmailField emergencyCEmail; + + private final H2 mt; + private final H3 fs; + private final H3 ss; public EmployeeView(final EmployeeService employeeService) { super(Employee.class); this.employeeService = employeeService; - name = new TextField(); - name.setWidthFull(); - name.setLabel("Name"); + + mt = new H2("Información General del Empleado"); + fs = new H3("Información Personal"); + ss = new H3("Datos de Contacto de Emergencia"); + + final HorizontalLayout mainLayout = new HorizontalLayout(); + final VerticalLayout sidebar = createSidebar(); + final VerticalLayout contentLayout = createContentLayout(); + final VerticalLayout contentLayout2 = createContentLayout(); + + firstName = new TextField("Nombres: "); + firstName.setWidthFull(); + firstName.setMaxLength(30); + firstName.setRequired(true); + + lastName = new TextField("Apellidos"); + lastName.setWidthFull(); + lastName.setMaxLength(30); + lastName.setRequired(true); + + status = new ComboBox<>("Estado"); + status.setItems(List.of("ACTIVE", "INACTIVE")); + lastName.setWidthFull(); + lastName.setMaxLength(30); + status.setRequired(true); + + birthday = new VDatePicker("Fecha de Nacimiento"); + birthday.setWidthFull(); + + birthCity = new TextField("Ciudad y País de Nacimiento"); + birthCity.setWidthFull(); + birthCity.setMaxLength(20); + + maritalStatus = new ComboBox<>("Estado Civil"); + maritalStatus.setItems(List.of("Soltero", "Casado", "Viudo", "Divorciado")); + maritalStatus.setWidthFull(); + + residenceAddress = new TextField("Dirección de Residencia"); + residenceAddress.setWidthFull(); + residenceAddress.setMaxLength(50); + + phoneNumber = new TextField("Teléfono"); + phoneNumber.setWidthFull(); + phoneNumber.setMaxLength(8); + phoneNumber.setValueChangeMode(ValueChangeMode.EAGER); + phoneNumber.addValueChangeListener(e -> { + if (!e.getValue().matches("\\d*")) { + phoneNumber.setErrorMessage("El teléfono debe contener solo números."); + } + }); + + personalEmail = new EmailField("E-mail"); + personalEmail.setWidthFull(); + personalEmail.setMaxLength(30); + + emergencyCName = new TextField("Nombres y Apellidos de Contacto"); + emergencyCName.setWidthFull(); + emergencyCName.setMaxLength(50); + + emergencyCAddress = new TextField("Dirección de Contacto"); + emergencyCAddress.setWidthFull(); + emergencyCAddress.setMaxLength(50); + + emergencyCPhone = new TextField("Teléfono de Contacto"); + emergencyCPhone.setWidthFull(); + emergencyCPhone.setMaxLength(8); + emergencyCPhone.setValueChangeMode(ValueChangeMode.EAGER); + emergencyCPhone.addValueChangeListener(e -> { + if (!e.getValue().matches("\\d*")) { + emergencyCPhone.setErrorMessage("El teléfono debe contener solo números."); + } + }); + + emergencyCEmail = new EmailField("Email de Contacto"); + emergencyCEmail.setWidthFull(); + emergencyCEmail.setMaxLength(30); + + contentLayout.add( + mt, fs, firstName, lastName, status, birthday, birthCity, maritalStatus, + residenceAddress, phoneNumber, personalEmail); + + contentLayout2.add( + ss, emergencyCName, emergencyCAddress, emergencyCPhone, emergencyCEmail); setSavedHandler((SavedHandler) employee -> { - final Employee saved = employeeService.createOrUpdate(employee); - setEntityWithEnabledSave(saved); + if (validateForm()) { + final Employee saved = employeeService.createOrUpdate(employee); + Notification.show("Employee saved successfully."); + getUI().ifPresent(ui -> ui.navigate(EmployeesListView.class)); + setEntityWithEnabledSave(saved); + } else { + Notification.show("Please complete the required fields correctly.", 3000, Notification.Position.MIDDLE); + } }); + + mainLayout.add(sidebar, contentLayout, contentLayout2); + addClassName("main-layout"); + } + + private boolean validateForm() { + return !firstName.isEmpty() && !lastName.isEmpty() && status.getValue() != null; + } + + private VerticalLayout createSidebar() { + VerticalLayout sidebar = new VerticalLayout(); + sidebar.setWidth("250px"); + sidebar.add(new Button("Información General", e -> navigateToSection("Información General"))); + sidebar.add(new Button("Detalles Profesionales", e -> navigateToSection("Detalles Profesionales"))); + return sidebar; + } + + private void navigateToSection(final String section) { + Notification.show("Navigating to " + section); + } + + private VerticalLayout createContentLayout() { + VerticalLayout contentLayout = new VerticalLayout(); + contentLayout.setWidth("100%"); + return contentLayout; } @Override @@ -53,7 +191,24 @@ public class EmployeeView extends BeanValidationForm implements HasUrl @Override protected List getFormComponents() { - return List.of(name); + return List.of( + mt, + fs, + firstName, + lastName, + status, + birthday, + birthCity, + maritalStatus, + residenceAddress, + phoneNumber, + personalEmail, + ss, + emergencyCName, + emergencyCAddress, + emergencyCPhone, + emergencyCEmail + ); } } diff --git a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java index 59fbb3f..489ded8 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java @@ -2,27 +2,20 @@ package com.primefactorsolutions.views; import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.service.EmployeeService; -import com.vaadin.flow.component.ClickEvent; -import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.data.provider.DataProvider; -import com.vaadin.flow.data.provider.DataProviderListener; -import com.vaadin.flow.data.provider.Query; -import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; -import com.vaadin.flow.shared.Registration; import com.vaadin.flow.spring.annotation.SpringComponent; +import org.vaadin.firitin.components.grid.PagingGrid; +import com.vaadin.flow.component.grid.GridSortOrder; +import com.vaadin.flow.data.provider.SortDirection; import jakarta.annotation.security.PermitAll; import org.springframework.context.annotation.Scope; -import org.vaadin.firitin.components.grid.VGrid; -import java.util.stream.Stream; +import java.util.List; + @SpringComponent @Scope("prototype") @@ -30,97 +23,87 @@ import java.util.stream.Stream; @Route(value = "/employees", layout = MainLayout.class) @PermitAll public class EmployeesListView extends Main { + private final EmployeeService employeeService; + private final PagingGrid table = new PagingGrid<>(Employee.class); public EmployeesListView(final EmployeeService employeeService) { this.employeeService = employeeService; - final H2 title = new H2("Employees list"); - final HorizontalLayout hl = new HorizontalLayout(); - final HorizontalLayout hf = new HorizontalLayout(); - final Button employeeListAscendingOrder = new Button("Employee List in Ascending Order"); - employeeListAscendingOrder.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - hl.add(employeeListAscendingOrder); - final Button employeeListDescendingOrder = new Button("Employee List in Descending Order"); - employeeListDescendingOrder.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - hl.add(employeeListDescendingOrder); - final VGrid grid = new VGrid<>(Employee.class); - grid.setColumns("firstName", "lastName", "status"); - grid.setAllRowsVisible(true); - grid.addComponentColumn((ValueProvider) employee -> { - ComboBox statusComboBox = new ComboBox<>(); - statusComboBox.setItems("Active", "Inactive"); - return statusComboBox; - }).setHeader("Change Status"); - grid.addComponentColumn((ValueProvider) employee -> { - final Button edit = new Button("Edit"); - edit.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - return edit; - }); - grid.addComponentColumn((ValueProvider) employee -> { - final Button save = new Button("Save"); - save.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - return save; - }); - - grid.setDataProvider(new DataProvider<>() { - @Override - public boolean isInMemory() { - return false; - } - - @Override - public int size(final Query query) { - return employeeService.getEmployees().size(); - } - - @Override - public Stream fetch(final Query query) { - int limit = query.getLimit(); - int pagerSize = query.getPageSize(); - int page = query.getPage(); - return employeeService.getEmployees().stream(); - } - - @Override - public void refreshItem(final Employee employee) { - // no-op - } - - @Override - public void refreshAll() { - // no-op - } - - @Override - public Registration addDataProviderListener(final DataProviderListener dataProviderListener) { - return null; - } - }); - - final Button previous = new Button("Previous"); - previous.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - hf.add(previous); - final Button next = new Button("Next"); - next.addClickListener((ComponentEventListener>) buttonClickEvent -> { - // no-op - }); - hf.add(next); - final Button addEmployee = new Button("Add Employee"); - addEmployee.addClickListener((ComponentEventListener>) buttonClickEvent -> { - this.getUI().get().navigate(EmployeeView.class, "new"); - }); - hf.add(addEmployee); - - add(title, hl, grid, hf); + setupView(); + refreshGrid(); } -} + + private void setupView() { + add(new H2("Employee List")); + configureTable(); + add(createAddEmployeeButton()); + add(table); + } + + private void configureTable() { + table.setColumns("firstName", "lastName", "status"); + addEditButtonColumn("Edit", this::navigateToEditView); + setupPagingGrid(); + } + + private void updateEmployeeStatus(final Employee employee, final boolean isActive) { + employee.setStatus(isActive ? Employee.Status.ACTIVE : Employee.Status.INACTIVE); + employeeService.createOrUpdate(employee); + refreshGrid(); + } + + private void addEditButtonColumn(final String label, final ButtonClickHandler handler) { + table.addComponentColumn(employee -> createButton(label, () -> handler.handle(employee))); + } + + private Button createButton(final String label, final Runnable onClickAction) { + Button button = new Button(label); + button.addClickListener(event -> onClickAction.run()); + return button; + } + + private Button createAddEmployeeButton() { + return createButton("Add Employee", this::navigateToAddEmployeeView); + } + + private void navigateToEditView(final Employee employee) { + getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, employee.getId().toString())); + } + + private void navigateToAddEmployeeView() { + getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, "new")); + } + + private void setupPagingGrid() { + table.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); + table.setPageSize(5); + } + + private void refreshGrid() { + table.setPagingDataProvider((page, pageSize) -> fetchEmployees((int) page, pageSize)); + } + + private List fetchEmployees(final int page, final int pageSize) { + int start = page * pageSize; + if (hasSortOrder()) { + return fetchSortedEmployees(start, pageSize); + } + return employeeService.findEmployees(start, pageSize); + } + + private boolean hasSortOrder() { + return !table.getSortOrder().isEmpty(); + } + + private List fetchSortedEmployees(final int start, final int pageSize) { + GridSortOrder sortOrder = table.getSortOrder().getFirst(); + return employeeService.findEmployees(start, pageSize, + sortOrder.getSorted().getKey(), + sortOrder.getDirection() == SortDirection.ASCENDING); + } + + @FunctionalInterface + private interface ButtonClickHandler { + void handle(Employee employee); + } +} \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index f797344..0888a8a 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -13,4 +13,15 @@ insert into employee (id, version, first_name, last_name, status) values ('e99b7 insert into employee (id, version, first_name, last_name, status) values ('f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 1, 'Ana', 'Garcia Rojas', 'ACTIVE'); insert into employee (id, version, first_name, last_name, status) values ('2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 1, 'Carlos', 'Lopez Mendoza', 'INACTIVE'); insert into employee (id, version, first_name, last_name, status) values ('4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 1, 'Maria', 'Fernandez Villca', 'ACTIVE'); -insert into employee (id, version, first_name, last_name, status) values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'Luis', 'Gutierrez Mamani', 'ACTIVE'); \ No newline at end of file +insert into employee (id, version, first_name, last_name, status) values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'Luis', 'Gutierrez Mamani', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 1, 'Laura', 'Martinez Paredes', 'INACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('6e6a8a4e-9f6b-44eb-8c69-40acfdc86756', 1, 'Roberto', 'Santos Escobar', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('36b0d1c6-bdc0-4d98-94bb-08b9bce3f0d5', 1, 'Valeria', 'Morales Ochoa', 'INACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('5a1c6d80-58b3-43e3-a5a5-24b4a2d1d54a', 1, 'Jorge', 'Ramirez Tapia', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('9d6a5b2e-6d0b-4b89-8d6a-d3f3d1bfc047', 1, 'Sandra', 'Torres Huanca', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('f8b3e0c0-0d5a-4e5c-bf9d-207b9b5e8279', 1, 'Felipe', 'Quispe Huanca', 'INACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 1, 'Gabriela', 'Rivas Arana', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('62d3c1b7-815e-4e96-8d7e-f8c4236bca55', 1, 'Oscar', 'Flores Quiroga', 'INACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('f20b7c5a-5a67-44f0-9ec1-4c1b8e80de05', 1, 'Marta', 'Vargas Soria', 'ACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('19b5a76e-d7b1-4b76-8b02-4d0748e85809', 1, 'Andres', 'Espinoza Chura', 'INACTIVE'); +insert into employee (id, version, first_name, last_name, status) values ('5c1a7b82-832d-4f24-8377-54b77b91b6a8', 1, 'Carla', 'Villanueva Arce', 'ACTIVE'); \ No newline at end of file