// Application.java
package com.aksenov.monitor;
import com.aksenov.monitor.repositories.CityRepository;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
import javax.sql.DataSource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
/**
 * The entry point of the Spring Boot application.
 *
 * Use the @PWA annotation make the application installable on phones, tablets
 * and some desktop browsers.
 *
 */
@SpringBootApplication
@ComponentScan(basePackages = {"com.aksenov.monitor"})
@Theme(value = "monitor")
public class Application implements AppShellConfigurator {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Bean
    SqlDataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
            SqlInitializationProperties properties, CityRepository repository) {
        // This bean ensures the database is only initialized when empty
        return new SqlDataSourceScriptDatabaseInitializer(dataSource, properties) {
            @Override
            public boolean initializeDatabase() {
                if (repository.count() == 0L) {
                    return super.initializeDatabase();
                }
                return false;
            }
        };
    }
}

// AbstractEntity.java
package com.aksenov.monitor.data;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Version;
@MappedSuperclass
public abstract class AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgenerator")
    // The initial value is to account for data.sql demo data ids
    @SequenceGenerator(name = "idgenerator", initialValue = 1000)
    private Long id;
    @Version
    private int version;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public int getVersion() {
        return version;
    }
    @Override
    public int hashCode() {
        if (getId() != null) {
            return getId().hashCode();
        }
        return super.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof AbstractEntity that)) {
            return false; // null or not an AbstractEntity class
        }
        if (getId() != null) {
            return getId().equals(that.getId());
        }
        return super.equals(that);
    }
}

// City.java
package com.aksenov.monitor.data;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
@Entity
@Data
@Table(name = "City")
public class City extends AbstractEntity {
    @Column(name = "name")
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

// Customer.java
package com.aksenov.monitor.data;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import lombok.Data;
@Entity
@Data
@Table(name = "Customer")
public class Customer extends AbstractEntity {
    private String name;
    @Email
    private String email;
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "city_id", nullable = false)
    private City city;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public City getCity() {
        return city;
    }
    public void setCityID(City cityID) {
        this.city = cityID;
    }
    public String getCityName() {
        return city.getName();
    }
}

// Manufacturer.java
package com.aksenov.monitor.data;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
@Entity
@Data
@Table(name = "Manufacturer")
public class Manufacturer extends AbstractEntity {
    private String name;
    private String country;
    private String contactInfo;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getContactInfo() {
        return contactInfo;
    }
    public void setContactInfo(String contactInfo) {
        this.contactInfo = contactInfo;
    }
}

// Monitor.java
package com.aksenov.monitor.data;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Data
@Table(name = "Monitor")
public class Monitor extends AbstractEntity {
    @Column(name = "model")
    private String model;
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "manufacturer_id", nullable = false)
    private Manufacturer manufacturer;
    private Integer screenSize;
    @Column(name = "resolution")
    private String resolution;
    private Integer refreshrRate;
    private Integer price;
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public Manufacturer getManufacturer() {
        return manufacturer;
    }
    public void setManufacturer(Manufacturer manufacturer) {
        this.manufacturer = manufacturer;
    }
    public Integer getScreenSize() {
        return screenSize;
    }
    public void setScreenSize(Integer screenSize) {
        this.screenSize = screenSize;
    }
    public String getResolution() {
        return resolution;
    }
    public void setResolution(String resolution) {
        this.resolution = resolution;
    }
    public Integer getRefreshrRate() {
        return refreshrRate;
    }
    public void setRefreshrRate(Integer refreshrRate) {
        this.refreshrRate = refreshrRate;
    }
    public Integer getPrice() {
        return price;
    }
    public void setPrice(Integer price) {
        this.price = price;
    }
}

// package-info.java
@NonNullApi
package com.aksenov.monitor.data;
import org.springframework.lang.NonNullApi;

// Sales.java
package com.aksenov.monitor.data;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Entity
@Data
@Table(name = "Sales")
public class Sales extends AbstractEntity {
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "monitor_id", nullable = false)
    private Monitor monitor;
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
    private LocalDateTime saleDate;
    private Integer quantity;
    private Integer totalPrice;
    public Monitor getMonitor() {
        return monitor;
    }
    public void setMonitor(Monitor monitor) {
        this.monitor = monitor;
    }
    public Customer getCustomer() {
        return customer;
    }
    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
    public LocalDateTime getSaleDate() {
        return saleDate;
    }
    public void setSaleDate(LocalDateTime saleDate) {
        this.saleDate = saleDate;
    }
    public Integer getQuantity() {
        return quantity;
    }
    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
    public Integer getTotalPrice() {
        return totalPrice;
    }
    public void setTotalPrice(Integer totalPrice) {
        this.totalPrice = totalPrice;
    }
}

// CityRepository.java
package com.aksenov.monitor.repositories;
import com.aksenov.monitor.data.City;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CityRepository extends JpaRepository<City, Long>, JpaSpecificationExecutor<City> {
}

// CustomerRepository.java
package com.aksenov.monitor.repositories;
import com.aksenov.monitor.data.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}

// ManufacturerRepository.java
package com.aksenov.monitor.repositories;
import com.aksenov.monitor.data.Manufacturer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ManufacturerRepository
        extends
            JpaRepository<Manufacturer, Long>,
            JpaSpecificationExecutor<Manufacturer> {
}

// MonitorRepository.java
package com.aksenov.monitor.repositories;
import com.aksenov.monitor.data.Monitor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface MonitorRepository extends JpaRepository<Monitor, Long>, JpaSpecificationExecutor<Monitor> {
}

// SalesRepository.java
package com.aksenov.monitor.repositories;
import com.aksenov.monitor.data.Sales;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Map;
public interface SalesRepository extends JpaRepository<Sales, Long>, JpaSpecificationExecutor<Sales> {
    @Query("SELECT s FROM Sales s WHERE FUNCTION('MONTH', s.saleDate) = :month AND FUNCTION('YEAR', s.saleDate) = :year")
    List<Sales> findAllBySaleDateMonthAndYear(@Param("month") int month, @Param("year") int year);
    @Query("SELECT SUM(s.totalPrice) FROM Sales s WHERE FUNCTION('MONTH', s.saleDate) = :month AND FUNCTION('YEAR', s.saleDate) = :year")
    Double findTotalSalesByMonthAndYear(@Param("month") int month, @Param("year") int year);
    @Query("SELECT m.model, SUM(s.totalPrice) FROM Sales s JOIN s.monitor m GROUP BY m.model")
    List<Object[]> getSalesStatisticsByModel();
}

// CityService.java
package com.aksenov.monitor.services;
import com.aksenov.monitor.data.City;
import com.aksenov.monitor.repositories.CityRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class CityService {
    private final CityRepository repository;
    @Autowired
    public CityService(CityRepository repository) {
        this.repository = repository;
    }
    public Optional<City> get(Long id) {
        return repository.findById(id);
    }
    public City save(City entity) {
        return repository.save(entity);
    }
    public void delete(Long id) {
        repository.deleteById(id);
    }
    public Page<City> list(Pageable pageable) {
        return repository.findAll(pageable);
    }
    public Page<City> list(Pageable pageable, Specification<City> filter) {
        return repository.findAll(filter, pageable);
    }
    public int count() {
        return (int) repository.count();
    }
    public List<City> list() {
        return repository.findAll();
    }
}

// CustomerService.java
package com.aksenov.monitor.services;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.repositories.CustomerRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
    private final CustomerRepository repository;
    public CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }
    public Optional<Customer> get(Long id) {
        return repository.findById(id);
    }
    public Customer save(Customer entity) {
        return repository.save(entity);
    }
    public void delete(Long id) {
        repository.deleteById(id);
    }
    public Page<Customer> list(Pageable pageable) {
        return repository.findAll(pageable);
    }
    public Page<Customer> list(Pageable pageable, Specification<Customer> filter) {
        return repository.findAll(filter, pageable);
    }
    public int count() {
        return (int) repository.count();
    }
    public List<Customer> list() {
        return repository.findAll();
    }
}

// ManufacturerService.java
package com.aksenov.monitor.services;
import com.aksenov.monitor.data.Manufacturer;
import com.aksenov.monitor.repositories.ManufacturerRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class ManufacturerService {
    private final ManufacturerRepository repository;
    public ManufacturerService(ManufacturerRepository repository) {
        this.repository = repository;
    }
    public Optional<Manufacturer> get(Long id) {
        return repository.findById(id);
    }
    public Manufacturer save(Manufacturer entity) {
        return repository.save(entity);
    }
    public void delete(Long id) {
        repository.deleteById(id);
    }
    public Page<Manufacturer> list(Pageable pageable) {
        return repository.findAll(pageable);
    }
    public Page<Manufacturer> list(Pageable pageable, Specification<Manufacturer> filter) {
        return repository.findAll(filter, pageable);
    }
    public List<Manufacturer> list() {
        return repository.findAll();
    }
    public int count() {
        return (int) repository.count();
    }
}

// MonitorService.java
package com.aksenov.monitor.services;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.data.Monitor;
import com.aksenov.monitor.repositories.MonitorRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class MonitorService {
    private final MonitorRepository repository;
    public MonitorService(MonitorRepository repository) {
        this.repository = repository;
    }
    public Optional<Monitor> get(Long id) {
        return repository.findById(id);
    }
    public Monitor save(Monitor entity) {
        return repository.save(entity);
    }
    public void delete(Long id) {
        repository.deleteById(id);
    }
    public Page<Monitor> list(Pageable pageable) {
        return repository.findAll(pageable);
    }
    public Page<Monitor> list(Pageable pageable, Specification<Monitor> filter) {
        return repository.findAll(filter, pageable);
    }
    public int count() {
        return (int) repository.count();
    }
    public List<Monitor> list() {
        return repository.findAll();
    }
}

// package-info.java
@NonNullApi
package com.aksenov.monitor.services;
import org.springframework.lang.NonNullApi;

// SalesService.java
package com.aksenov.monitor.services;
import com.aksenov.monitor.data.Sales;
import com.aksenov.monitor.repositories.SalesRepository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class SalesService {
    private final SalesRepository repository;
    public SalesService(SalesRepository repository) {
        this.repository = repository;
    }
    public Optional<Sales> get(Long id) {
        return repository.findById(id);
    }
    public Sales save(Sales entity) {
        return repository.save(entity);
    }
    public void delete(Long id) {
        repository.deleteById(id);
    }
    public Page<Sales> list(Pageable pageable) {
        return repository.findAll(pageable);
    }
    public Page<Sales> list(Pageable pageable, Specification<Sales> filter) {
        return repository.findAll(filter, pageable);
    }
    public int count() {
        return (int) repository.count();
    }
    public List<Sales> list() {
        return repository.findAll();
    }
    public List<Sales> findSalesByMonthAndYear(int month, int year) {
        return repository.findAllBySaleDateMonthAndYear(month, year);
    }
    public Double getTotalSalesForMonthAndYear(int month, int year) {
        return repository.findTotalSalesByMonthAndYear(month, year);
    }
    public Map<String, Long> getSalesDataByModel() {
        Map<String, Long> salesData = new HashMap<>();
        List<Object[]> results = repository.getSalesStatisticsByModel();
        for (Object[] result : results) {
            String model = (String) result[0];
            Long total = (Long) result[1];
            salesData.put(model, total);
        }
        return salesData;
    }
}

// CityView.java
package com.aksenov.monitor.views.city;
import com.aksenov.monitor.data.City;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.services.CityService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("City")
@Route("/:cityID?/:action?(edit)")
@Menu(order = 0, icon = LineAwesomeIconUrl.CITY_SOLID)
@RouteAlias("")
public class CityView extends Div implements BeforeEnterObserver {
    private final String CITY_ID = "cityID";
    private final String CITY_EDIT_ROUTE_TEMPLATE = "/%s/edit";
    private final Grid<City> grid = new Grid<>(City.class, false);
    private TextField name;
    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");
    private final BeanValidationBinder<City> binder;
    private City city;
    private List<City> cities;
    private final CityService cityService;
    private ListDataProvider<City> dataProvider;
    public CityView(CityService cityService) {
        this.cityService = cityService;
        addClassNames("city-view");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);
        add(splitLayout);
        // Configure Grid
        grid.addColumn("name").setAutoWidth(true);
        //grid.setItems(query -> cityService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        cities = cityService.list();
        dataProvider = new ListDataProvider<>(cities);
        grid.setDataProvider(dataProvider);
        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(CITY_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(CityView.class);
            }
        });
        // Configure Form
        binder = new BeanValidationBinder<>(City.class);
        // Bind fields. This is where you'd define e.g. validation rules
        binder.bindInstanceFields(this);
        addFilterRow();
        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });
        delete.addClickListener(e -> {
            try {
                if (this.city == null) {
                    return;
                }
                binder.writeBean(this.city);
                cityService.delete(this.city.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(CityView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
        save.addClickListener(e -> {
            try {
                if (this.city == null) {
                    this.city = new City();
                }
                if (name.isEmpty() ) {
                    Notification.show("Please fill all required fields");
                    return;
                }
                binder.writeBean(this.city);
                cityService.save(this.city);
                clearForm();
                refreshGrid();
                Notification.show("Data updated");
                UI.getCurrent().navigate(CityView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> cityId = event.getRouteParameters().get(CITY_ID).map(Long::parseLong);
        if (cityId.isPresent()) {
            Optional<City> cityFromBackend = cityService.get(cityId.get());
            if (cityFromBackend.isPresent()) {
                populateForm(cityFromBackend.get());
            } else {
                Notification.show(String.format("The requested city was not found, ID = %s", cityId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(CityView.class);
            }
        }
    }
    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();
        // Filter for name column
        TextField nameFilter = new TextField();
        nameFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(city -> city.getName().toLowerCase().contains(filterValue.toLowerCase()));
            }
            // Clear other filters
            clearOtherFilters(nameFilter);
            clearForm();
            refreshGrid();
        });
        nameFilter.setPlaceholder("Filter");
        nameFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("name")).setComponent(nameFilter);
    }
    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != nameFilter) {
            nameFilter.clear();
        }
    }
    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");
        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);
        FormLayout formLayout = new FormLayout();
        name = new TextField("Name");
        formLayout.add(name);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);
        splitLayout.addToSecondary(editorLayoutDiv);
    }
    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }
    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }
    private void refreshGrid() {
        SerializablePredicate<City> currentFilter = dataProvider.getFilter();
        // 2. Обновить список
        cities = cityService.list();
        // 3. Создать новый dataProvider и применить фильтр
        dataProvider = new ListDataProvider<>(cities);
        grid.setDataProvider(dataProvider);
        dataProvider.setFilter(currentFilter);
        //  grid.select(null);
        dataProvider.refreshAll();
    }
    private void clearForm() {
        populateForm(null);
    }
    private void populateForm(City value) {
        this.city = value;
        binder.readBean(this.city);
    }
    private TextField nameFilter;
}

// CustomerView.java
package com.aksenov.monitor.views.customer;
import com.aksenov.monitor.data.City;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.services.CityService;
import com.aksenov.monitor.services.CustomerService;
import com.aksenov.monitor.views.city.CityView;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.util.List;
import java.util.Optional;
import java.util.logging.Filter;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("Customer")
@Route("customer/:customerID?/:action?(edit)")
@Menu(order = 1, icon = LineAwesomeIconUrl.ADDRESS_CARD)
public class CustomerView extends Div implements BeforeEnterObserver {
    private final String CUSTOMER_ID = "customerID";
    private final String CUSTOMER_EDIT_ROUTE_TEMPLATE = "customer/%s/edit";
    private final Grid<Customer> grid = new Grid<>(Customer.class, false);
    private TextField name;
    private TextField email;
    private ComboBox<City> city;
    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");
    private final BeanValidationBinder<Customer> binder;
    private Customer customer;
    private List<Customer> customers;
    private final CustomerService customerService;
    private final CityService cityService;
    private ListDataProvider<Customer> dataProvider;
    public CustomerView(CustomerService customerService, CityService cityService) {
        this.customerService = customerService;
        this.cityService = cityService;
        addClassNames("customer-view");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);
        add(splitLayout);
        // Configure Grid
        grid.addColumn("name").setAutoWidth(true);
        grid.addColumn("email").setAutoWidth(true);
        grid.addColumn("city.name").setAutoWidth(true).setHeader("City");
        //grid.setItems(query -> customerService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        customers = customerService.list();
        dataProvider = new ListDataProvider<>(customers);
        grid.setDataProvider(dataProvider);
        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(CUSTOMER_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(CustomerView.class);
            }
        });
        // Configure Form
        binder = new BeanValidationBinder<>(Customer.class);
        addFilterRow();
        // Bind fields. This is where you'd define e.g. validation rules
        binder.bindInstanceFields(this);
        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });
        save.addClickListener(e -> {
            try {
                if (this.customer == null) {
                    this.customer = new Customer();
                }
                if (name.isEmpty() || email.isEmpty() || city.isEmpty() ) {
                    Notification.show("Please fill all required fields");
                    return;
                }
                binder.writeBean(this.customer);
                customerService.save(this.customer);
                clearForm();
                refreshGrid();
                Notification.show("Data updated");
                UI.getCurrent().navigate(CustomerView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
        delete.addClickListener(e -> {
            try {
                if (this.city == null) {
                    return;
                }
                binder.writeBean(this.customer);
                customerService.delete(this.customer.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(CustomerView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> customerId = event.getRouteParameters().get(CUSTOMER_ID).map(Long::parseLong);
        if (customerId.isPresent()) {
            Optional<Customer> customerFromBackend = customerService.get(customerId.get());
            if (customerFromBackend.isPresent()) {
                populateForm(customerFromBackend.get());
            } else {
                Notification.show(String.format("The requested customer was not found, ID = %s", customerId.get()),
                        3000, Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(CustomerView.class);
            }
        }
    }
    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();
        // Filter for name column
        nameFilter = new TextField();
        nameFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(customer -> customer.getName().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(nameFilter);
            clearForm();
            refreshGrid();
        });
        nameFilter.setPlaceholder("Filter");
        nameFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("name")).setComponent(nameFilter);
        // Filter for email column
        emailFilter = new TextField();
        emailFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(customer -> customer.getEmail().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(emailFilter);
            clearForm();
            refreshGrid();
        });
        emailFilter.setPlaceholder("Filter");
        emailFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("email")).setComponent(emailFilter);
        // Filter for cityName column
        cityFilter = new TextField();
        cityFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            dataProvider.setFilter(customer -> customer.getCityName() != null && customer.getCityName().toLowerCase().contains(filterValue.toLowerCase()));
            clearOtherFilters(cityFilter);
            clearForm();
            refreshGrid();
        });
        cityFilter.setPlaceholder("Filter");
        cityFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("city.name")).setComponent(cityFilter);
    }
    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != nameFilter) {
            nameFilter.clear();
        }
        if (currentFilter != emailFilter) {
            emailFilter.clear();
        }
        if (currentFilter != cityFilter) {
            cityFilter.clear();
        }
    }
    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");
        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);
        FormLayout formLayout = new FormLayout();
        name = new TextField("Name");
        email = new TextField("Email");
        city = new ComboBox<City>("City");
        city.setItemLabelGenerator(currCity -> String.valueOf(currCity.getId()));
        city.setAllowCustomValue(false);
        city.addFocusListener(event -> {
            List<City> cityList = cityService.list();
            city.setItems(cityList);
        });
        city.setItemLabelGenerator(City::getName);
        formLayout.add(name, email, city);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);
        splitLayout.addToSecondary(editorLayoutDiv);
    }
    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }
    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }
    private void refreshGrid() {
        SerializablePredicate<Customer> currentFilter = dataProvider.getFilter();
        // 2. Обновить список
        customers = customerService.list();
        // 3. Создать новый dataProvider и применить фильтр
        dataProvider = new ListDataProvider<>(customers);
        grid.setDataProvider(dataProvider);
        dataProvider.setFilter(currentFilter);
        //  grid.select(null);
        dataProvider.refreshAll();
    }
    private void clearForm() {
        populateForm(null);
    }
    private void populateForm(Customer value) {
        this.customer = value;
        // Загрузить список городов
        List<City> cityList = cityService.list();
        // Установить список городов в ComboBox
        city.setItems(cityList);
        // Установить значения клиента
        binder.readBean(this.customer);
        if (value != null && value.getCity() != null) {
            city.setValue(value.getCity());
        } else {
            city.clear();
        }
    }
    private TextField nameFilter;
    private TextField emailFilter;
    private TextField cityFilter;
}

// DashboardView.java
package com.aksenov.monitor.views.dashboard;
import com.aksenov.monitor.data.Sales;
import com.aksenov.monitor.repositories.*;
import com.aksenov.monitor.services.SalesService;
import com.aksenov.monitor.views.dashboard.ServiceHealth.Status;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.board.Board;
import com.vaadin.flow.component.charts.Chart;
import com.vaadin.flow.component.charts.model.*;
import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.theme.lumo.LumoUtility.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("Dashboard")
@Route("dashboard")
@Menu(order = 6, icon = LineAwesomeIconUrl.CHART_AREA_SOLID)
@SpringComponent
@Scope("prototype")
public class DashboardView extends Main {
    private final CityRepository cityRepository;
    private final CustomerRepository customerRepository;
    private final ManufacturerRepository manufacturerRepository;
    private final MonitorRepository monitorRepository;
    private final SalesService salesService;
    private final SalesRepository salesRepository;
    public DashboardView(CityRepository cityRepository, CustomerRepository customerRepository, ManufacturerRepository manufacturerRepository, MonitorRepository monitorRepository, SalesService salesService, SalesRepository salesRepository) {
        this.cityRepository = cityRepository;
        this.customerRepository = customerRepository;
        this.manufacturerRepository = manufacturerRepository;
        this.monitorRepository = monitorRepository;
        this.salesService = salesService;
        this.salesRepository = salesRepository;
        addClassName("dashboard-view");
        Board board = new Board();
        board.addRow(
                createHighlight("Current amount of sales", String.valueOf(salesRepository.findTotalSalesByMonthAndYear(LocalDateTime.now().getMonthValue(), LocalDateTime.now().getYear())), 33.7),
                createHighlight("Current sales", String.valueOf((long) salesRepository.findAllBySaleDateMonthAndYear(LocalDateTime.now().getMonthValue(), LocalDateTime.now().getYear()).size()), 33.7),
                createHighlight("Current customer", String.valueOf(customerRepository.count()), -112.45));
        board.addRow(createViewEvents());
        board.addRow(createResponseTimes());
        add(board);
    }
    private List<Series> getSalesSeries(int year) {
        List<Series> seriesList = new ArrayList<>();
        String[] categories = new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        List<DataSeriesItem> dataSeriesItems = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            List<Sales> sales = salesService.findSalesByMonthAndYear(i + 1, year);
            int salesCount = sales != null ? sales.size() : 0;
            dataSeriesItems.add(new DataSeriesItem(categories[i], salesCount));
        }
        DataSeries dataSeries = new DataSeries();
        dataSeries.setData(dataSeriesItems);
        seriesList.add(dataSeries);
        return seriesList;
    }
    private Component createHighlight(String title, String value, Double percentage) {
        VaadinIcon icon = VaadinIcon.ARROW_UP;
        String prefix = "";
        String theme = "badge";
        if (percentage == 0) {
            prefix = "±";
        } else if (percentage > 0) {
            prefix = "+";
            theme += " success";
        } else if (percentage < 0) {
            icon = VaadinIcon.ARROW_DOWN;
            theme += " error";
        }
        H2 h2 = new H2(title);
        h2.addClassNames(FontWeight.NORMAL, Margin.NONE, TextColor.SECONDARY, FontSize.XSMALL);
        Span span = new Span(value);
        span.addClassNames(FontWeight.SEMIBOLD, FontSize.XXXLARGE);
        Icon i = icon.create();
        i.addClassNames(BoxSizing.BORDER, Padding.XSMALL);
        Span badge = new Span(i, new Span(prefix + percentage.toString()));
        badge.getElement().getThemeList().add(theme);
        VerticalLayout layout = new VerticalLayout(h2, span, badge);
        layout.addClassName(Padding.LARGE);
        layout.setPadding(false);
        layout.setSpacing(false);
        return layout;
    }
    private Component createViewEvents() {
        // Header
        Select<String> year = new Select<>();
        year.setItems("2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025");
        year.setValue("2024");
        year.setWidth("100px");
        // Chart
        Chart chart = new Chart(ChartType.AREASPLINE);
        Configuration conf = chart.getConfiguration();
        conf.getChart().setStyledMode(true);
        XAxis xAxis = new XAxis();
        xAxis.setCategories("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
        conf.addxAxis(xAxis);
        conf.getyAxis().setTitle("Values");
        PlotOptionsAreaspline plotOptions = new PlotOptionsAreaspline();
        plotOptions.setPointPlacement(PointPlacement.ON);
        plotOptions.setMarker(new Marker(false));
        conf.addPlotOptions(plotOptions);
        //  Load data for sales chart.
        List<Series> salesSeries = getSalesSeries(Integer.parseInt(year.getValue()));
        conf.setSeries(salesSeries);
        year.addValueChangeListener(event -> {
            chart.getConfiguration().setSeries(getSalesSeries(Integer.parseInt(event.getValue())));
            chart.drawChart();
        });
        // Add it all together
        HorizontalLayout header = createHeader("Sales events", "Sales/month");
        header.add(year);
        VerticalLayout viewEvents = new VerticalLayout(header, chart);
        viewEvents.addClassName(Padding.LARGE);
        viewEvents.setPadding(false);
        viewEvents.setSpacing(false);
        viewEvents.getElement().getThemeList().add("spacing-l");
        return viewEvents;
    }
    private Component createResponseTimes() {
        HorizontalLayout header = createHeader("Sales by Model", "Average across all models");
        // Chart
        Chart chart = new Chart(ChartType.PIE);
        Configuration conf = chart.getConfiguration();
        conf.getChart().setStyledMode(true);
        chart.setThemeName("gradient");
        DataSeries series = new DataSeries();
        Map<String, Long> salesData = salesService.getSalesDataByModel();
        for (Map.Entry<String, Long> entry : salesData.entrySet()) {
            series.add(new DataSeriesItem(entry.getKey(), entry.getValue()));
        }
        conf.addSeries(series);
        // Add it all together
        VerticalLayout serviceHealth = new VerticalLayout(header, chart);
        serviceHealth.addClassName(Padding.LARGE);
        serviceHealth.setPadding(false);
        serviceHealth.setSpacing(false);
        serviceHealth.getElement().getThemeList().add("spacing-l");
        return serviceHealth;
    }
    private HorizontalLayout createHeader(String title, String subtitle) {
        H2 h2 = new H2(title);
        h2.addClassNames(FontSize.XLARGE, Margin.NONE);
        Span span = new Span(subtitle);
        span.addClassNames(TextColor.SECONDARY, FontSize.XSMALL);
        VerticalLayout column = new VerticalLayout(h2, span);
        column.setPadding(false);
        column.setSpacing(false);
        HorizontalLayout header = new HorizontalLayout(column);
        header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        header.setSpacing(false);
        header.setWidthFull();
        return header;
    }
    private String getStatusDisplayName(ServiceHealth serviceHealth) {
        Status status = serviceHealth.getStatus();
        if (status == Status.OK) {
            return "Ok";
        } else if (status == Status.FAILING) {
            return "Failing";
        } else if (status == Status.EXCELLENT) {
            return "Excellent";
        } else {
            return status.toString();
        }
    }
    private String getStatusTheme(ServiceHealth serviceHealth) {
        Status status = serviceHealth.getStatus();
        String theme = "badge primary small";
        if (status == Status.EXCELLENT) {
            theme += " success";
        } else if (status == Status.FAILING) {
            theme += " error";
        }
        return theme;
    }
}

// ServiceHealth.java
package com.aksenov.monitor.views.dashboard;
/**
 * Simple DTO class for the inbox list to demonstrate complex object data
 */
public class ServiceHealth {
    private Status status;
    private String city;
    private int input;
    private int output;
    private String theme;
    enum Status {
        EXCELLENT, OK, FAILING;
    }
    public ServiceHealth() {
    }
    public ServiceHealth(Status status, String city, int input, int output) {
        this.status = status;
        this.city = city;
        this.input = input;
        this.output = output;
    }
    public Status getStatus() {
        return status;
    }
    public void setStatus(Status status) {
        this.status = status;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public int getInput() {
        return input;
    }
    public void setInput(int input) {
        this.input = input;
    }
    public int getOutput() {
        return output;
    }
    public void setOutput(int output) {
        this.output = output;
    }
}

// MainLayout.java
package com.aksenov.monitor.views;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.Layout;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.server.menu.MenuConfiguration;
import com.vaadin.flow.server.menu.MenuEntry;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.util.List;
/**
 * The main view is a top-level placeholder for other views.
 */
@Layout
@AnonymousAllowed
public class MainLayout extends AppLayout {
    private H1 viewTitle;
    public MainLayout() {
        setPrimarySection(Section.DRAWER);
        addDrawerContent();
        addHeaderContent();
    }
    private void addHeaderContent() {
        DrawerToggle toggle = new DrawerToggle();
        toggle.setAriaLabel("Menu toggle");
        viewTitle = new H1();
        viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
        addToNavbar(true, toggle, viewTitle);
    }
    private void addDrawerContent() {
        Span appName = new Span("Monitor");
        appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE);
        Header header = new Header(appName);
        Scroller scroller = new Scroller(createNavigation());
        addToDrawer(header, scroller, createFooter());
    }
    private SideNav createNavigation() {
        SideNav nav = new SideNav();
        List<MenuEntry> menuEntries = MenuConfiguration.getMenuEntries();
        menuEntries.forEach(entry -> {
            if (entry.icon() != null) {
                nav.addItem(new SideNavItem(entry.title(), entry.path(), new SvgIcon(entry.icon())));
            } else {
                nav.addItem(new SideNavItem(entry.title(), entry.path()));
            }
        });
        return nav;
    }
    private Footer createFooter() {
        Footer layout = new Footer();
        return layout;
    }
    @Override
    protected void afterNavigation() {
        super.afterNavigation();
        viewTitle.setText(getCurrentPageTitle());
    }
    private String getCurrentPageTitle() {
        return MenuConfiguration.getPageHeader(getContent()).orElse("");
    }
}

// ManufacturerView.java
package com.aksenov.monitor.views.manufacturer;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.data.Manufacturer;
import com.aksenov.monitor.services.ManufacturerService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("Manufacturer")
@Route("manufacturer/:manufacturerID?/:action?(edit)")
@Menu(order = 2, icon = LineAwesomeIconUrl.HAMMER_SOLID)
public class ManufacturerView extends Div implements BeforeEnterObserver {
    private final String MANUFACTURER_ID = "manufacturerID";
    private final String MANUFACTURER_EDIT_ROUTE_TEMPLATE = "manufacturer/%s/edit";
    private final Grid<Manufacturer> grid = new Grid<>(Manufacturer.class, false);
    private TextField name;
    private TextField country;
    private TextField contactInfo;
    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");
    private final BeanValidationBinder<Manufacturer> binder;
    private Manufacturer manufacturer;
    private List<Manufacturer> manufacturers;
    private final ManufacturerService manufacturerService;
    private ListDataProvider<Manufacturer> dataProvider;
    public ManufacturerView(ManufacturerService manufacturerService) {
        this.manufacturerService = manufacturerService;
        addClassNames("manufacturer-view");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);
        add(splitLayout);
        // Configure Grid
        grid.addColumn("name").setAutoWidth(true);
        grid.addColumn("country").setAutoWidth(true);
        grid.addColumn("contactInfo").setAutoWidth(true);
        //grid.setItems(query -> manufacturerService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        manufacturers = manufacturerService.list();
        dataProvider = new ListDataProvider<>(manufacturers);
        grid.setDataProvider(dataProvider);
        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(MANUFACTURER_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(ManufacturerView.class);
            }
        });
        // Configure Form
        binder = new BeanValidationBinder<>(Manufacturer.class);
        addFilterRow();
        binder.bindInstanceFields(this);
        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });
        save.addClickListener(e -> {
            try {
                if (this.manufacturer == null) {
                    this.manufacturer = new Manufacturer();
                }
                if (name.isEmpty() || country.isEmpty() || contactInfo.isEmpty()) {
                    Notification.show("Please fill all required fields");
                    return;
                }
                binder.writeBean(this.manufacturer);
                manufacturerService.save(this.manufacturer);
                clearForm();
                refreshGrid();
                Notification.show("Data updated");
                UI.getCurrent().navigate(ManufacturerView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
        delete.addClickListener(e -> {
            try {
                if (this.manufacturer == null) {
                    return;
                }
                binder.writeBean(this.manufacturer);
                manufacturerService.delete(this.manufacturer.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(ManufacturerView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> manufacturerId = event.getRouteParameters().get(MANUFACTURER_ID).map(Long::parseLong);
        if (manufacturerId.isPresent()) {
            Optional<Manufacturer> manufacturerFromBackend = manufacturerService.get(manufacturerId.get());
            if (manufacturerFromBackend.isPresent()) {
                populateForm(manufacturerFromBackend.get());
            } else {
                Notification.show(
                        String.format("The requested manufacturer was not found, ID = %s", manufacturerId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(ManufacturerView.class);
            }
        }
    }
    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();
        // Filter for name column
        nameFilter = new TextField();
        nameFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(manufacturer -> manufacturer.getName() != null && manufacturer.getName().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(nameFilter);
            clearForm();
            refreshGrid();
        });
        nameFilter.setPlaceholder("Filter");
        nameFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("name")).setComponent(nameFilter);
        // Filter for country column
        countryFilter = new TextField();
        countryFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(manufacturer ->  manufacturer.getCountry() != null && manufacturer.getCountry().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(countryFilter);
            clearForm();
            refreshGrid();
        });
        countryFilter.setPlaceholder("Filter");
        countryFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("country")).setComponent(countryFilter);
        // Filter for contactInfo column
        contactInfoFilter = new TextField();
        contactInfoFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            dataProvider.setFilter(manufacturer -> manufacturer.getContactInfo() != null && manufacturer.getContactInfo().toLowerCase().contains(filterValue.toLowerCase()));
            clearOtherFilters(contactInfoFilter);
            clearForm();
            refreshGrid();
        });
        contactInfoFilter.setPlaceholder("Filter");
        contactInfoFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("contactInfo")).setComponent(contactInfoFilter);
    }
    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != nameFilter) {
            nameFilter.clear();
        }
        if (currentFilter != countryFilter) {
            countryFilter.clear();
        }
        if (currentFilter != contactInfoFilter) {
            contactInfoFilter.clear();
        }
    }
    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");
        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);
        FormLayout formLayout = new FormLayout();
        name = new TextField("Name");
        country = new TextField("Country");
        contactInfo = new TextField("Contact Info");
        formLayout.add(name, country, contactInfo);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);
        splitLayout.addToSecondary(editorLayoutDiv);
    }
    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }
    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }
    private void refreshGrid() {
        SerializablePredicate<Manufacturer> currentFilter = dataProvider.getFilter();
        // 2. Обновить список
        manufacturers = manufacturerService.list();
        // 3. Создать новый dataProvider и применить фильтр
        dataProvider = new ListDataProvider<>(manufacturers);
        grid.setDataProvider(dataProvider);
        dataProvider.setFilter(currentFilter);
        //  grid.select(null);
        dataProvider.refreshAll();
    }
    private void clearForm() {
        populateForm(null);
    }
    private void populateForm(Manufacturer value) {
        this.manufacturer = value;
        binder.readBean(this.manufacturer);
    }
    private TextField nameFilter;
    private TextField countryFilter;
    private TextField contactInfoFilter;
}

// MonitorView.java
package com.aksenov.monitor.views.monitor;
import com.aksenov.monitor.data.Manufacturer;
import com.aksenov.monitor.data.Monitor;
import com.aksenov.monitor.services.ManufacturerService;
import com.aksenov.monitor.services.MonitorService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.converter.StringToIntegerConverter;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.util.List;
import java.util.Optional;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("Monitor")
@Route("monitor/:monitorID?/:action?(edit)")
@Menu(order = 3, icon = LineAwesomeIconUrl.TV_SOLID)
public class MonitorView extends Div implements BeforeEnterObserver {
    private final String MONITOR_ID = "monitorID";
    private final String MONITOR_EDIT_ROUTE_TEMPLATE = "monitor/%s/edit";
    private final Grid<Monitor> grid = new Grid<>(Monitor.class, false);
    private TextField model;
    private ComboBox<Manufacturer> manufacturer;
    private TextField screenSize;
    private TextField resolution;
    private TextField refreshrRate;
    private TextField price;
    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");
    private final BeanValidationBinder<Monitor> binder;
    private Monitor monitor;
    private List<Monitor> monitors;
    private final MonitorService monitorService;
    private final ManufacturerService manufacturerService;
    private ListDataProvider<Monitor> dataProvider;
    public MonitorView(MonitorService monitorService, ManufacturerService manufacturerService) {
        this.monitorService = monitorService;
        this.manufacturerService = manufacturerService;
        addClassNames("monitor-view");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);
        add(splitLayout);
        // Configure Grid
        grid.addColumn("model").setAutoWidth(true);
        grid.addColumn("manufacturer.name").setAutoWidth(true).setHeader("Manufacturer");
        grid.addColumn("screenSize").setAutoWidth(true);
        grid.addColumn("resolution").setAutoWidth(true);
        grid.addColumn("refreshrRate").setAutoWidth(true);
        grid.addColumn("price").setAutoWidth(true);
        //grid.setItems(query -> monitorService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        monitors = monitorService.list();
        dataProvider = new ListDataProvider<>(monitors);
        grid.setDataProvider(dataProvider);
        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(MONITOR_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(MonitorView.class);
            }
        });
        // Configure Form
        binder = new BeanValidationBinder<>(Monitor.class);
        // Bind fields. This is where you'd define e.g. validation rules
        binder.forField(model).bind("model");
        binder.forField(manufacturer).bind("manufacturer");
        binder.forField(resolution).bind("resolution");
        binder.forField(screenSize).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("screenSize");
        binder.forField(refreshrRate).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("refreshrRate");
        binder.forField(price).withConverter(new StringToIntegerConverter("Only numbers are allowed")).bind("price");
        addFilterRow();
        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });
        save.addClickListener(e -> {
            try {
                if (this.monitor == null) {
                    this.monitor = new Monitor();
                }
                if (model.isEmpty() || manufacturer.isEmpty() || screenSize.isEmpty() || resolution.isEmpty() ||
                        refreshrRate.isEmpty() || price.isEmpty()) {
                    Notification.show("Please fill all required fields");
                    return;
                }
                if (binder.validate().isOk()) {
                    binder.writeBean(this.monitor);
                    monitorService.save(this.monitor);
                    clearForm();
                    refreshGrid();
                    Notification.show("Data updated");
                    UI.getCurrent().navigate(MonitorView.class);
                }else{
                    Notification.show("Please fill all required fields");
                }
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
        delete.addClickListener(e -> {
            try {
                if (this.monitor == null) {
                    return;
                }
                binder.writeBean(this.monitor);
                monitorService.delete(this.monitor.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(MonitorView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> monitorId = event.getRouteParameters().get(MONITOR_ID).map(Long::parseLong);
        if (monitorId.isPresent()) {
            Optional<Monitor> monitorFromBackend = monitorService.get(monitorId.get());
            if (monitorFromBackend.isPresent()) {
                populateForm(monitorFromBackend.get());
            } else {
                Notification.show(String.format("The requested monitor was not found, ID = %s", monitorId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(MonitorView.class);
            }
        }
    }
    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();
        // Filter for model column
        modelFilter = new TextField();
        modelFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if (filterValue.isEmpty()) {
                dataProvider.clearFilters();
            }
            else {
                dataProvider.setFilter(monitor -> monitor.getModel().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(modelFilter);
            clearForm();
            refreshGrid();
        });
        modelFilter.setPlaceholder("Filter");
        modelFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("model")).setComponent(modelFilter);
        // Filter for manufacturerId column
        manufacturerIdFilter = new TextField();
        manufacturerIdFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> String.valueOf(monitor.getManufacturer()).contains(filterValue));
            }
            clearOtherFilters(manufacturerIdFilter);
            clearForm();
            refreshGrid();
        });
        manufacturerIdFilter.setPlaceholder("Filter");
        manufacturerIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("manufacturer.name")).setComponent(manufacturerIdFilter);
        // Filter for screenSize column
        screenSizeFilter = new TextField();
        screenSizeFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> String.valueOf(monitor.getScreenSize()).contains(filterValue));
            }
            clearOtherFilters(screenSizeFilter);
            clearForm();
            refreshGrid();
        });
        screenSizeFilter.setPlaceholder("Filter");
        screenSizeFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("screenSize")).setComponent(screenSizeFilter);
        // Filter for resolution column
        resolutionFilter = new TextField();
        resolutionFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> monitor.getResolution().toLowerCase().contains(filterValue.toLowerCase()));
            }
            clearOtherFilters(resolutionFilter);
            clearForm();
            refreshGrid();
        });
        resolutionFilter.setPlaceholder("Filter");
        resolutionFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("resolution")).setComponent(resolutionFilter);
        // Filter for refreshrRate column
        refreshrRateFilter = new TextField();
        refreshrRateFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> String.valueOf(monitor.getRefreshrRate()).contains(filterValue));
            }
            clearOtherFilters(refreshrRateFilter);
            clearForm();
            refreshGrid();
        });
        refreshrRateFilter.setPlaceholder("Filter");
        refreshrRateFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("refreshrRate")).setComponent(refreshrRateFilter);
        // Filter for price column
        priceFilter = new TextField();
        priceFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(monitor -> String.valueOf(monitor.getPrice()).contains(filterValue));
            }
            clearOtherFilters(priceFilter);
            clearForm();
            refreshGrid();
        });
        priceFilter.setPlaceholder("Filter");
        priceFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("price")).setComponent(priceFilter);
    }
    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != modelFilter) {
            modelFilter.clear();
        }
        if (currentFilter != manufacturerIdFilter) {
            manufacturerIdFilter.clear();
        }
        if (currentFilter != screenSizeFilter) {
            screenSizeFilter.clear();
        }
        if (currentFilter != resolutionFilter) {
            resolutionFilter.clear();
        }
        if (currentFilter != refreshrRateFilter) {
            refreshrRateFilter.clear();
        }
        if (currentFilter != priceFilter) {
            priceFilter.clear();
        }
    }
    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");
        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);
        FormLayout formLayout = new FormLayout();
        model = new TextField("Model");
        model.setRequired(true);
        manufacturer = new ComboBox<Manufacturer>("Manufacturer");
        manufacturer.setItemLabelGenerator(currManufacturer -> String.valueOf(currManufacturer.getId()));
        manufacturer.setAllowCustomValue(false);
        manufacturer.setRequired(true);
        manufacturer.addFocusListener(event -> {
            List<Manufacturer> manufacturerList = manufacturerService.list();
            manufacturer.setItems(manufacturerList);
        });
        manufacturer.setItemLabelGenerator(manufacturer ->
                manufacturer != null ? manufacturer.getName() : "");
        screenSize = new TextField("Screen Size");
        screenSize.setRequired(true);
        resolution = new TextField("Resolution");
        resolution.setRequired(true);
        refreshrRate = new TextField("Refreshr Rate");
        refreshrRate.setRequired(true);
        price = new TextField("Price");
        price.setRequired(true);
        formLayout.add(model, manufacturer, screenSize, resolution, refreshrRate, price);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);
        splitLayout.addToSecondary(editorLayoutDiv);
    }
    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }
    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }
    private void refreshGrid() {
        SerializablePredicate<Monitor> currentFilter = dataProvider.getFilter();
        // 2. Обновить список
        monitors = monitorService.list();
        // 3. Создать новый dataProvider и применить фильтр
        dataProvider = new ListDataProvider<>(monitors);
        grid.setDataProvider(dataProvider);
        dataProvider.setFilter(currentFilter);
        //  grid.select(null);
        dataProvider.refreshAll();
    }
    private void clearForm() {
        populateForm(null);
    }
    private void populateForm(Monitor value) {
        this.monitor = value;
        // Загрузить список городов
        List<Manufacturer> manufacturerList = manufacturerService.list();
        // Установить список городов в ComboBox
        manufacturer.setItems(manufacturerList);
        binder.readBean(this.monitor);
    }
    private TextField modelFilter;
    private TextField manufacturerIdFilter;
    private TextField screenSizeFilter;
    private TextField resolutionFilter;
    private TextField refreshrRateFilter;
    private TextField priceFilter;
}

// SalesView.java
package com.aksenov.monitor.views.sales;
import com.aksenov.monitor.data.Customer;
import com.aksenov.monitor.data.Monitor;
import com.aksenov.monitor.data.Sales;
import com.aksenov.monitor.services.CustomerService;
import com.aksenov.monitor.services.MonitorService;
import com.aksenov.monitor.services.SalesService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.Notification.Position;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.converter.StringToIntegerConverter;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.lineawesome.LineAwesomeIconUrl;
@PageTitle("Sales")
@Route("sales/:salesID?/:action?(edit)")
@Menu(order = 4, icon = LineAwesomeIconUrl.DOLLAR_SIGN_SOLID)
public class SalesView extends Div implements BeforeEnterObserver {
    private final String SALES_ID = "salesID";
    private final String SALES_EDIT_ROUTE_TEMPLATE = "sales/%s/edit";
    private final Grid<Sales> grid = new Grid<>(Sales.class, false);
    private ComboBox<Monitor> monitor;
    private ComboBox<Customer> customer;
    private DateTimePicker saleDate;
    private TextField quantity;
    private TextField totalPrice;
    private final Button cancel = new Button("Cancel");
    private final Button save = new Button("Save");
    private final Button delete = new Button("Delete");
    private final BeanValidationBinder<Sales> binder;
    private Sales sales;
    private List<Sales> salesList;
    private ListDataProvider<Sales> dataProvider;
    private final SalesService salesService;
    private final MonitorService monitorService;
    private final CustomerService customerService;
    public SalesView(SalesService salesService, MonitorService monitorService, CustomerService customerService) {
        this.salesService = salesService;
        this.monitorService = monitorService;
        this.customerService = customerService;
        addClassNames("sales-view");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);
        add(splitLayout);
        // Configure Grid
        grid.addColumn("monitor.model").setAutoWidth(true).setHeader("Monitor");
        grid.addColumn("customer.name").setAutoWidth(true).setHeader("Customer");
        grid.addColumn("saleDate").setAutoWidth(true);
        grid.addColumn("quantity").setAutoWidth(true);
        grid.addColumn("totalPrice").setAutoWidth(true);
        //grid.setItems(query -> salesService.list(VaadinSpringDataHelpers.toSpringPageRequest(query)).stream());
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        salesList = salesService.list();
        dataProvider = new ListDataProvider<>(salesList);
        grid.setDataProvider(dataProvider);
        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(SALES_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(SalesView.class);
            }
        });
        // Configure Form
        binder = new BeanValidationBinder<>(Sales.class);
        // Bind fields. This is where you'd define e.g. validation rules
        binder.forField(monitor).bind("monitor");
        binder.forField(customer).bind("customer");
        binder.forField(saleDate).bind("saleDate");
        binder.forField(quantity).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("quantity");
        binder.forField(totalPrice).withConverter(new StringToIntegerConverter("Only numbers are allowed"))
                .bind("totalPrice");
        addFilterRow();
        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });
        save.addClickListener(e -> {
            try {
                if (this.sales == null) {
                    this.sales = new Sales();
                }
                if (monitor.isEmpty() || customer.isEmpty() || saleDate.isEmpty()
                        || quantity.isEmpty() ){
                    Notification.show("Please fill all required fields");
                    return;
                }
                // Check if the binder has errors before writing
                if (binder.validate().isOk()) {
                    binder.writeBean(this.sales);
                    salesService.save(this.sales);
                    clearForm();
                    refreshGrid();
                    Notification.show("Data updated");
                    UI.getCurrent().navigate(SalesView.class);
                }else{
                    Notification.show("Please fill all required fields");
                }
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
        delete.addClickListener(e -> {
            try {
                if (this.sales == null) {
                    return;
                }
                binder.writeBean(this.sales);
                salesService.delete(this.sales.getId());
                clearForm();
                refreshGrid();
                Notification.show("Data deleted");
                UI.getCurrent().navigate(SalesView.class);
            } catch (ObjectOptimisticLockingFailureException exception) {
                Notification n = Notification.show(
                        "Error updating the data. Somebody else has updated the record while you were making changes.");
                n.setPosition(Position.MIDDLE);
                n.addThemeVariants(NotificationVariant.LUMO_ERROR);
            } catch (ValidationException validationException) {
                Notification.show("Failed to update the data. Check again that all values are valid");
            }
        });
    }
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Long> salesId = event.getRouteParameters().get(SALES_ID).map(Long::parseLong);
        if (salesId.isPresent()) {
            Optional<Sales> salesFromBackend = salesService.get(salesId.get());
            if (salesFromBackend.isPresent()) {
                populateForm(salesFromBackend.get());
            } else {
                Notification.show(String.format("The requested sales was not found, ID = %s", salesId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(SalesView.class);
            }
        }
    }
    private void addFilterRow() {
        HeaderRow headerRow = grid.appendHeaderRow();
        // Filter for monitor column
        monitorIdFilter = new TextField();
        monitorIdFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getMonitor()).contains(filterValue));
            }
            clearOtherFilters(monitorIdFilter);
            clearForm();
            refreshGrid();
        });
        monitorIdFilter.setPlaceholder("Filter");
        monitorIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("monitor.model")).setComponent(monitorIdFilter);
        // Filter for customerId column
        customerIdFilter = new TextField();
        customerIdFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getCustomer()).contains(filterValue));
            }
            clearOtherFilters(customerIdFilter);
            clearForm();
            refreshGrid();
        });
        customerIdFilter.setPlaceholder("Filter");
        customerIdFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("customer.name")).setComponent(customerIdFilter);
        // Filter for saleDate column
        saleDateFilter = new TextField();
        saleDateFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getSaleDate()).contains(filterValue));
            }
            clearOtherFilters(saleDateFilter);
            clearForm();
            refreshGrid();
        });
        saleDateFilter.setPlaceholder("Filter");
        saleDateFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("saleDate")).setComponent(saleDateFilter);
        // Filter for quantity column
        quantityFilter = new TextField();
        quantityFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getQuantity()).contains(filterValue));
            }
            clearOtherFilters(quantityFilter);
            clearForm();
            refreshGrid();
        });
        quantityFilter.setPlaceholder("Filter");
        quantityFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("quantity")).setComponent(quantityFilter);
        // Filter for totalPrice column
        totalPriceFilter = new TextField();
        totalPriceFilter.addValueChangeListener(event -> {
            String filterValue = ((TextField) event.getSource()).getValue();
            if(filterValue.isEmpty()) {
                dataProvider.clearFilters();
            } else {
                dataProvider.setFilter(sales -> String.valueOf(sales.getTotalPrice()).contains(filterValue));
            }
            clearOtherFilters(totalPriceFilter);
            clearForm();
            refreshGrid();
        });
        totalPriceFilter.setPlaceholder("Filter");
        totalPriceFilter.setWidthFull();
        headerRow.getCell(grid.getColumnByKey("totalPrice")).setComponent(totalPriceFilter);
    }
    private void clearOtherFilters(TextField currentFilter) {
        if (currentFilter != monitorIdFilter) {
            monitorIdFilter.clear();
        }
        if (currentFilter != customerIdFilter) {
            customerIdFilter.clear();
        }
        if (currentFilter != saleDateFilter) {
            saleDateFilter.clear();
        }
        if (currentFilter != quantityFilter) {
            quantityFilter.clear();
        }
        if (currentFilter != totalPriceFilter) {
            totalPriceFilter.clear();
        }
    }
    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("editor-layout");
        Div editorDiv = new Div();
        editorDiv.setClassName("editor");
        editorLayoutDiv.add(editorDiv);
        FormLayout formLayout = new FormLayout();
        monitor = new ComboBox<Monitor>("Monitor");
        monitor.setItemLabelGenerator(currMonitor -> String.valueOf(currMonitor.getId()));
        monitor.setAllowCustomValue(false);
        monitor.setRequired(true);  // Make it required
        monitor.addValueChangeListener(event -> {
            if(event.getValue() != null) {
                calculateTotalPrice();
            } else {
                totalPrice.clear();
            }
        });
        monitor.addFocusListener(event -> {
            List<Monitor> manufacturerList = monitorService.list();
            monitor.setItems(manufacturerList);
        });
        monitor.setItemLabelGenerator(monitor ->
                monitor != null ? monitor.getModel() : "");
        customer = new ComboBox<Customer>("Customer");
        customer.setItemLabelGenerator(currMonitor -> String.valueOf(currMonitor.getId()));
        customer.setAllowCustomValue(false);
        customer.setRequired(true);  // Make it required
        customer.addFocusListener(event -> {
            List<Customer> customerList = customerService.list();
            customer.setItems(customerList);
        });
        customer.setItemLabelGenerator(customer ->
                customer != null ? customer.getName() : "");
        saleDate = new DateTimePicker("Sale Date");
        saleDate.setStep(Duration.ofSeconds(1));
        saleDate.setRequiredIndicatorVisible(true); // Make it required
        quantity = new TextField("Quantity");
        quantity.setRequired(true); // Make it required
        quantity.addValueChangeListener(event -> calculateTotalPrice());
        totalPrice = new TextField("Total Price");
        totalPrice.setReadOnly(true);
        formLayout.add(monitor, customer, saleDate, quantity, totalPrice);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);
        splitLayout.addToSecondary(editorLayoutDiv);
    }
    private void calculateTotalPrice() {
        if(monitor.getValue() != null && !quantity.isEmpty()) {
            Integer quantityValue = Integer.parseInt(quantity.getValue());
            Integer monitorPrice = monitor.getValue().getPrice();
            Integer totalPriceValue = quantityValue * monitorPrice;
            totalPrice.setValue(String.valueOf(totalPriceValue));
        } else {
            totalPrice.clear();
        }
    }
    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("button-layout");
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }
    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setClassName("grid-wrapper");
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }
    private void refreshGrid() {
        SerializablePredicate<Sales> currentFilter = dataProvider.getFilter();
        salesList = salesService.list();
        dataProvider = new ListDataProvider<>(salesList);
        grid.setDataProvider(dataProvider);
        dataProvider.setFilter(currentFilter);
        dataProvider.refreshAll();
    }
    private void clearForm() {
        populateForm(null);
    }
    private void populateForm(Sales value) {
        this.sales = value;
        // Загрузить список городов
        List<Monitor> monitorList = monitorService.list();
        // Установить список городов в ComboBox
        monitor.setItems(monitorList);
        List<Customer> customerList = customerService.list();
        // Установить список городов в ComboBox
        customer.setItems(customerList);
        // Установить значения клиента
        binder.readBean(this.sales);
        if (value != null && value.getMonitor() != null) {
            monitor.setValue(value.getMonitor());
        } else {
            monitor.clear();
        }
        if (value != null && value.getCustomer() != null) {
            customer.setValue(value.getCustomer());
        } else {
            customer.clear();
        }
        if(value != null && value.getSaleDate() != null) {
            saleDate.setValue(value.getSaleDate()); // Set the date from the Sales entity
        } else {
            saleDate.clear();
        }
        calculateTotalPrice();
    }
    private TextField monitorIdFilter;
    private TextField customerIdFilter;
    private TextField saleDateFilter;
    private TextField quantityFilter;
    private TextField totalPriceFilter;
}

