diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientOverviewController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientOverviewController.java index 085209e..d683b51 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientOverviewController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientOverviewController.java @@ -1,23 +1,177 @@ package org.chainoptim.desktop.features.client.controller; -import javafx.fxml.FXML; -import javafx.scene.control.Label; +import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; +import org.chainoptim.desktop.features.client.dto.ClientOverviewDTO; import org.chainoptim.desktop.features.client.model.Client; -import org.chainoptim.desktop.features.factory.model.Factory; -import org.chainoptim.desktop.shared.search.model.SearchData; +import org.chainoptim.desktop.features.client.service.ClientService; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; import org.chainoptim.desktop.shared.util.DataReceiver; +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.time.format.DateTimeFormatter; +import java.util.List; public class ClientOverviewController implements DataReceiver { + private final ClientService clientService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + + private final FallbackManager fallbackManager; + private Client client; @FXML - private Label clientName; + private VBox detailsVBox; + @FXML + private VBox entitiesVBox; + + @Inject + public ClientOverviewController(ClientService clientService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager) { + this.clientService = clientService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fallbackManager = fallbackManager; + } @Override public void setData(Client client) { this.client = client; - clientName.setText(client.getName()); - System.out.println("Client received in overview: " + client.getName()); + + loadClientOverview(); + } + + private void loadClientOverview() { + if (client == null) { + return; + } + + fallbackManager.reset(); + fallbackManager.setLoading(true); + + clientService.getClientOverview(client.getId()) + .thenApply(this::handleOverviewResponse) + .exceptionally(this::handleOverviewException); + } + + private Result handleOverviewResponse(Result result) { + Platform.runLater(() -> { + if (result.getError() != null) { + fallbackManager.setErrorMessage("Failed to load client overview"); + return; + } + fallbackManager.setLoading(false); + ClientOverviewDTO clientOverviewDTO = result.getData(); + + renderUI(clientOverviewDTO); + }); + return result; + } + + private Result handleOverviewException(Throwable throwable) { + Platform.runLater(() -> + fallbackManager.setErrorMessage("Failed to load client overview") + ); + return new Result<>(); + } + + private void renderUI(ClientOverviewDTO clientOverviewDTO) { + renderDates(); + renderLocation(); + renderEntityFlowPane("Supplied Products", clientOverviewDTO.getSuppliedProducts(), "Product"); + renderEntityFlowPane("Delivered from Factories", clientOverviewDTO.getDeliveredFromFactories(), "Factory"); + renderEntityFlowPane("Delivered from Warehouses", clientOverviewDTO.getDeliveredFromWarehouses(), "Warehouse"); + } + + private void renderDates() { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy, hh:mm a"); + String formattedCreatedAt = client.getCreatedAt() != null ? client.getCreatedAt().format(dateTimeFormatter) : "None"; + String formattedUpdatedAt = client.getUpdatedAt() != null ? client.getUpdatedAt().format(dateTimeFormatter) : "None"; + renderField("Created At: ", formattedCreatedAt); + renderField("Last Modified: ", formattedUpdatedAt); + } + + private void renderLocation() { + if (client.getLocation() == null) { + return; + } + + Label locationLabel = new Label("• Location"); + locationLabel.getStyleClass().setAll("general-label-large"); + detailsVBox.getChildren().add(locationLabel); + + renderField("Address: ", client.getLocation().getAddress()); + renderField("City: ", client.getLocation().getCity()); + renderField("State: ", client.getLocation().getState()); + renderField("Country: ", client.getLocation().getCountry()); + renderField("Zip Code: ", client.getLocation().getZipCode()); + renderField("Latitude: ", client.getLocation().getLatitude() != null ? client.getLocation().getLatitude().toString() : ""); + renderField("Longitude: ", client.getLocation().getLongitude() != null ? client.getLocation().getLongitude().toString() : ""); + } + + private void renderField(String field, String fieldValue) { + HBox fieldHBox = new HBox(8); + fieldHBox.setAlignment(Pos.CENTER_LEFT); + Label countryLabel = new Label(field); + countryLabel.getStyleClass().setAll("general-label-medium-large"); + + Label countryValueLabel = new Label(); + countryValueLabel.getStyleClass().setAll("general-label"); + countryValueLabel.setText(fieldValue); + + if (fieldValue != null) { + fieldHBox.getChildren().addAll(countryLabel, countryValueLabel); + detailsVBox.getChildren().add(fieldHBox); + } } + + private void renderEntityFlowPane(String labelText, List entityDTOs, String entityPageKey) { + FlowPane entityFlowPane = new FlowPane(); + entityFlowPane.setHgap(8); + entityFlowPane.setVgap(8); + entityFlowPane.setAlignment(Pos.CENTER_LEFT); + + Label label = new Label(labelText + ":"); + label.getStyleClass().setAll("general-label-medium-large"); + entityFlowPane.getChildren().add(label); + + if (entityDTOs.isEmpty()) { + Label noEntitiesLabel = new Label("None"); + noEntitiesLabel.getStyleClass().setAll("general-label"); + entityFlowPane.getChildren().add(noEntitiesLabel); + entitiesVBox.getChildren().add(entityFlowPane); + return; + } + + for (int i = 0; i < entityDTOs.size(); i++) { + SmallEntityDTO entityDTO = entityDTOs.get(i); + String nameText = i != entityDTOs.size() - 1 ? entityDTO.getName() + ", " : entityDTO.getName(); + Label entityLabel = new Label(nameText); + entityLabel.getStyleClass().setAll("pseudo-link", "general-label"); + + entityLabel.setOnMouseClicked(event -> { + currentSelectionService.setSelectedId(entityDTO.getId()); + navigationService.switchView(entityPageKey + "?id=" + entityDTO.getId(), true, null); + }); + + entityFlowPane.getChildren().add(entityLabel); + } + + entitiesVBox.getChildren().add(entityFlowPane); + } + + } diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientShipmentsController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientShipmentsController.java index 2967bd2..9db9981 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientShipmentsController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientShipmentsController.java @@ -357,7 +357,7 @@ private Result> handleShipmentsResponse(Result< tableView.getItems().clear(); if (paginatedResults.results.isEmpty()) { - fallbackManager.setNoResults(true); +// fallbackManager.setNoResults(true); return; } diff --git a/src/main/java/org/chainoptim/desktop/features/client/dto/ClientOverviewDTO.java b/src/main/java/org/chainoptim/desktop/features/client/dto/ClientOverviewDTO.java new file mode 100644 index 0000000..0a9407d --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/client/dto/ClientOverviewDTO.java @@ -0,0 +1,19 @@ +package org.chainoptim.desktop.features.client.dto; + +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ClientOverviewDTO { + + + private List suppliedProducts; + private List deliveredFromFactories; + private List deliveredFromWarehouses; +} diff --git a/src/main/java/org/chainoptim/desktop/features/client/service/ClientService.java b/src/main/java/org/chainoptim/desktop/features/client/service/ClientService.java index 1f0f604..f338a43 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/service/ClientService.java +++ b/src/main/java/org/chainoptim/desktop/features/client/service/ClientService.java @@ -1,5 +1,6 @@ package org.chainoptim.desktop.features.client.service; +import org.chainoptim.desktop.features.client.dto.ClientOverviewDTO; import org.chainoptim.desktop.features.client.model.Client; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; @@ -17,4 +18,5 @@ CompletableFuture>> getClientsByOrganizationIdAd SearchParams searchParams ); CompletableFuture> getClientById(Integer clientId); + CompletableFuture> getClientOverview(Integer clientId); } diff --git a/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java index 66c13ea..cd6d380 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.features.client.service; import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.features.client.dto.ClientOverviewDTO; import org.chainoptim.desktop.features.client.model.Client; import org.chainoptim.desktop.shared.caching.CacheKeyBuilder; import org.chainoptim.desktop.shared.caching.CachingService; @@ -72,4 +73,12 @@ public CompletableFuture> getClientById(Integer clientId) { return requestHandler.sendRequest(request, new TypeReference() {}); } + + public CompletableFuture> getClientOverview(Integer clientId) { + String routeAddress = "http://localhost:8080/api/v1/clients/" + clientId.toString() + "/overview"; + + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference() {}); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/client/service/ClientShipmentsServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/client/service/ClientShipmentsServiceImpl.java index c77e842..97ee815 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/service/ClientShipmentsServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/client/service/ClientShipmentsServiceImpl.java @@ -52,7 +52,7 @@ public CompletableFuture>> getClientShip String rootAddress = "http://localhost:8080/api/v1/"; String cacheKey = CacheKeyBuilder.buildAdvancedSearchKey( "client-shipments", - searchMode == SearchMode.ORGANIZATION ? "organization" : "order", entityId.toString(), + searchMode == SearchMode.ORGANIZATION ? "organization" : "client", entityId.toString(), searchParams); String routeAddress = rootAddress + cacheKey; diff --git a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryOverviewController.java b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryOverviewController.java index 497d0e5..983ebc4 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryOverviewController.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryOverviewController.java @@ -1,21 +1,179 @@ package org.chainoptim.desktop.features.factory.controller; -import javafx.fxml.FXML; -import javafx.scene.control.Label; +import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; +import org.chainoptim.desktop.features.factory.dto.FactoryOverviewDTO; import org.chainoptim.desktop.features.factory.model.Factory; +import org.chainoptim.desktop.features.factory.service.FactoryService; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; import org.chainoptim.desktop.shared.util.DataReceiver; +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.time.format.DateTimeFormatter; +import java.util.List; public class FactoryOverviewController implements DataReceiver { + private final FactoryService factoryService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + + private final FallbackManager fallbackManager; + private Factory factory; @FXML - private Label factoryName; + private VBox detailsVBox; + @FXML + private VBox entitiesVBox; + + @Inject + public FactoryOverviewController(FactoryService factoryService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager) { + this.factoryService = factoryService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fallbackManager = fallbackManager; + } @Override public void setData(Factory factory) { this.factory = factory; - factoryName.setText(factory.getName()); - System.out.println("Factory received in overview: " + factory.getName()); + + loadFactoryOverview(); + } + + private void loadFactoryOverview() { + if (factory == null) { + return; + } + + fallbackManager.reset(); + fallbackManager.setLoading(true); + + factoryService.getFactoryOverview(factory.getId()) + .thenApply(this::handleOverviewResponse) + .exceptionally(this::handleOverviewException); + } + + private Result handleOverviewResponse(Result result) { + Platform.runLater(() -> { + if (result.getError() != null) { + fallbackManager.setErrorMessage("Failed to load factory overview"); + return; + } + fallbackManager.setLoading(false); + FactoryOverviewDTO factoryOverviewDTO = result.getData(); + + renderUI(factoryOverviewDTO); + }); + return result; + } + + private Result handleOverviewException(Throwable throwable) { + Platform.runLater(() -> + fallbackManager.setErrorMessage("Failed to load factory overview") + ); + return new Result<>(); + } + + private void renderUI(FactoryOverviewDTO factoryOverviewDTO) { + renderDates(); + renderLocation(); + renderEntityFlowPane("Factory Stages", factoryOverviewDTO.getFactoryStages(), "Stage"); + renderEntityFlowPane("Manufactured Components", factoryOverviewDTO.getManufacturedComponents(), "Component"); + renderEntityFlowPane("Manufactured Products", factoryOverviewDTO.getManufacturedProducts(), "Product"); + renderEntityFlowPane("Delivered From Suppliers", factoryOverviewDTO.getDeliveredFromSuppliers(), "Supplier"); + renderEntityFlowPane("Delivered To Clients", factoryOverviewDTO.getDeliveredToClients(), "Client"); + } + + private void renderDates() { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy, hh:mm a"); + String formattedCreatedAt = factory.getCreatedAt() != null ? factory.getCreatedAt().format(dateTimeFormatter) : "None"; + String formattedUpdatedAt = factory.getUpdatedAt() != null ? factory.getUpdatedAt().format(dateTimeFormatter) : "None"; + renderField("Created At: ", formattedCreatedAt); + renderField("Last Modified: ", formattedUpdatedAt); + } + + private void renderLocation() { + if (factory.getLocation() == null) { + return; + } + + Label locationLabel = new Label("• Location"); + locationLabel.getStyleClass().setAll("general-label-large"); + detailsVBox.getChildren().add(locationLabel); + + renderField("Address: ", factory.getLocation().getAddress()); + renderField("City: ", factory.getLocation().getCity()); + renderField("State: ", factory.getLocation().getState()); + renderField("Country: ", factory.getLocation().getCountry()); + renderField("Zip Code: ", factory.getLocation().getZipCode()); + renderField("Latitude: ", factory.getLocation().getLatitude() != null ? factory.getLocation().getLatitude().toString() : ""); + renderField("Longitude: ", factory.getLocation().getLongitude() != null ? factory.getLocation().getLongitude().toString() : ""); + } + + private void renderField(String field, String fieldValue) { + HBox fieldHBox = new HBox(8); + fieldHBox.setAlignment(Pos.CENTER_LEFT); + Label countryLabel = new Label(field); + countryLabel.getStyleClass().setAll("general-label-medium-large"); + + Label countryValueLabel = new Label(); + countryValueLabel.getStyleClass().setAll("general-label"); + countryValueLabel.setText(fieldValue); + + if (fieldValue != null) { + fieldHBox.getChildren().addAll(countryLabel, countryValueLabel); + detailsVBox.getChildren().add(fieldHBox); + } } + + private void renderEntityFlowPane(String labelText, List entityDTOs, String entityPageKey) { + FlowPane entityFlowPane = new FlowPane(); + entityFlowPane.setHgap(8); + entityFlowPane.setVgap(8); + entityFlowPane.setAlignment(Pos.CENTER_LEFT); + + Label label = new Label(labelText + ":"); + label.getStyleClass().setAll("general-label-medium-large"); + entityFlowPane.getChildren().add(label); + + if (entityDTOs.isEmpty()) { + Label noEntitiesLabel = new Label("None"); + noEntitiesLabel.getStyleClass().setAll("general-label"); + entityFlowPane.getChildren().add(noEntitiesLabel); + entitiesVBox.getChildren().add(entityFlowPane); + return; + } + + for (int i = 0; i < entityDTOs.size(); i++) { + SmallEntityDTO entityDTO = entityDTOs.get(i); + String nameText = i != entityDTOs.size() - 1 ? entityDTO.getName() + ", " : entityDTO.getName(); + Label entityLabel = new Label(nameText); + entityLabel.getStyleClass().setAll("pseudo-link", "general-label"); + + entityLabel.setOnMouseClicked(event -> { + currentSelectionService.setSelectedId(entityDTO.getId()); + navigationService.switchView(entityPageKey + "?id=" + entityDTO.getId(), true, null); + }); + + entityFlowPane.getChildren().add(entityLabel); + } + + entitiesVBox.getChildren().add(entityFlowPane); + } + + } diff --git a/src/main/java/org/chainoptim/desktop/features/factory/dto/FactoryOverviewDTO.java b/src/main/java/org/chainoptim/desktop/features/factory/dto/FactoryOverviewDTO.java new file mode 100644 index 0000000..f7538a2 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/factory/dto/FactoryOverviewDTO.java @@ -0,0 +1,20 @@ +package org.chainoptim.desktop.features.factory.dto; + +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FactoryOverviewDTO { + + private List factoryStages; + private List manufacturedComponents; + private List manufacturedProducts; + private List deliveredFromSuppliers; + private List deliveredToClients; +} diff --git a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryService.java b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryService.java index aace095..25ff5ea 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryService.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryService.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.features.factory.service; import org.chainoptim.desktop.features.factory.dto.FactoriesSearchDTO; +import org.chainoptim.desktop.features.factory.dto.FactoryOverviewDTO; import org.chainoptim.desktop.features.factory.model.Factory; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; @@ -19,4 +20,5 @@ CompletableFuture>> getFactoriesByOrganizationI SearchParams searchParams ); CompletableFuture> getFactoryById(Integer factoryId); + CompletableFuture> getFactoryOverview(Integer factoryId); } diff --git a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryServiceImpl.java index 13d8a51..0f3f272 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryServiceImpl.java @@ -2,6 +2,7 @@ import org.chainoptim.desktop.core.user.service.TokenManager; import org.chainoptim.desktop.features.factory.dto.FactoriesSearchDTO; +import org.chainoptim.desktop.features.factory.dto.FactoryOverviewDTO; import org.chainoptim.desktop.features.factory.model.Factory; import org.chainoptim.desktop.shared.caching.CacheKeyBuilder; import org.chainoptim.desktop.shared.caching.CachingService; @@ -81,4 +82,12 @@ public CompletableFuture> getFactoryById(Integer factoryId) { return requestHandler.sendRequest(request, new TypeReference() {}); } + + public CompletableFuture> getFactoryOverview(Integer factoryId) { + String routeAddress = "http://localhost:8080/api/v1/factories/" + factoryId.toString() + "/overview"; + + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference() {}); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/ComponentController.java b/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/ComponentController.java index c66654c..d46b248 100644 --- a/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/ComponentController.java +++ b/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/ComponentController.java @@ -84,7 +84,6 @@ private Result handleComponentResponse(Result result) { componentName.setText(component.getName()); componentDescription.setText(component.getDescription()); - System.out.println("Component: " + component); }); return result; diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierController.java index c0fd4de..072329e 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierController.java @@ -139,12 +139,12 @@ private void setUpTabListeners() { }); shipmentsTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { if (Boolean.TRUE.equals(isNowSelected) && shipmentsTab.getContent() == null) { - commonViewsLoader.loadTabContent(shipmentsTab, "/org/chainoptim/desktop/features/supplier/SupplierShipmentsView.fxml", this.supplier); + commonViewsLoader.loadTabContent(shipmentsTab, "/org/chainoptim/desktop/features/supplier/SupplierShipmentsView.fxml", new SearchData<>(this.supplier, SearchMode.SECONDARY)); } }); performanceTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { if (Boolean.TRUE.equals(isNowSelected) && performanceTab.getContent() == null) { - commonViewsLoader.loadTabContent(performanceTab, "/org/chainoptim/desktop/features/supplier/SupplierPerformanceView.fxml", this.supplier); + commonViewsLoader.loadTabContent(performanceTab, "/org/chainoptim/desktop/features/supplier/SupplierPerformanceView.fxml", new SearchData<>(this.supplier, SearchMode.SECONDARY)); } }); } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierOverviewController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierOverviewController.java index a3cccb6..498ec30 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierOverviewController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierOverviewController.java @@ -1,23 +1,178 @@ package org.chainoptim.desktop.features.supplier.controller; -import javafx.fxml.FXML; -import javafx.scene.control.Label; +import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; +import org.chainoptim.desktop.features.supplier.dto.SupplierOverviewDTO; import org.chainoptim.desktop.features.supplier.model.Supplier; -import org.chainoptim.desktop.shared.search.model.SearchData; +import org.chainoptim.desktop.features.supplier.service.SupplierService; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; import org.chainoptim.desktop.shared.util.DataReceiver; +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.time.format.DateTimeFormatter; +import java.util.List; + public class SupplierOverviewController implements DataReceiver { + private final SupplierService supplierService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + + private final FallbackManager fallbackManager; + private Supplier supplier; @FXML - private Label supplierName; + private VBox detailsVBox; + @FXML + private VBox entitiesVBox; + + @Inject + public SupplierOverviewController(SupplierService supplierService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager) { + this.supplierService = supplierService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fallbackManager = fallbackManager; + } @Override public void setData(Supplier supplier) { this.supplier = supplier; - supplierName.setText(supplier.getName()); - System.out.println("Supplier received in overview: " + supplier.getName()); + + loadSupplierOverview(); + } + + private void loadSupplierOverview() { + if (supplier == null) { + return; + } + + fallbackManager.reset(); + fallbackManager.setLoading(true); + + supplierService.getSupplierOverview(supplier.getId()) + .thenApply(this::handleOverviewResponse) + .exceptionally(this::handleOverviewException); + } + + private Result handleOverviewResponse(Result result) { + Platform.runLater(() -> { + if (result.getError() != null) { + fallbackManager.setErrorMessage("Failed to load supplier overview"); + return; + } + fallbackManager.setLoading(false); + SupplierOverviewDTO supplierOverviewDTO = result.getData(); + + renderUI(supplierOverviewDTO); + }); + return result; + } + + private Result handleOverviewException(Throwable throwable) { + Platform.runLater(() -> + fallbackManager.setErrorMessage("Failed to load supplier overview") + ); + return new Result<>(); } + private void renderUI(SupplierOverviewDTO supplierOverviewDTO) { + renderDates(); + renderLocation(); + renderEntityFlowPane("Supplied Components", supplierOverviewDTO.getSuppliedComponents(), "Component"); + renderEntityFlowPane("Delivered to Factories", supplierOverviewDTO.getDeliveredToFactories(), "Factory"); + renderEntityFlowPane("Delivered to Warehouses", supplierOverviewDTO.getDeliveredToWarehouses(), "Warehouse"); + } + + private void renderDates() { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy, hh:mm a"); + String formattedCreatedAt = supplier.getCreatedAt() != null ? supplier.getCreatedAt().format(dateTimeFormatter) : "None"; + String formattedUpdatedAt = supplier.getUpdatedAt() != null ? supplier.getUpdatedAt().format(dateTimeFormatter) : "None"; + renderField("Created At: ", formattedCreatedAt); + renderField("Last Modified: ", formattedUpdatedAt); + } + + private void renderLocation() { + if (supplier.getLocation() == null) { + return; + } + + Label locationLabel = new Label("• Location"); + locationLabel.getStyleClass().setAll("general-label-large"); + detailsVBox.getChildren().add(locationLabel); + + renderField("Address: ", supplier.getLocation().getAddress()); + renderField("City: ", supplier.getLocation().getCity()); + renderField("State: ", supplier.getLocation().getState()); + renderField("Country: ", supplier.getLocation().getCountry()); + renderField("Zip Code: ", supplier.getLocation().getZipCode()); + renderField("Latitude: ", supplier.getLocation().getLatitude() != null ? supplier.getLocation().getLatitude().toString() : ""); + renderField("Longitude: ", supplier.getLocation().getLongitude() != null ? supplier.getLocation().getLongitude().toString() : ""); + } + + private void renderField(String field, String fieldValue) { + HBox fieldHBox = new HBox(8); + fieldHBox.setAlignment(Pos.CENTER_LEFT); + Label countryLabel = new Label(field); + countryLabel.getStyleClass().setAll("general-label-medium-large"); + + Label countryValueLabel = new Label(); + countryValueLabel.getStyleClass().setAll("general-label"); + countryValueLabel.setText(fieldValue); + + if (fieldValue != null) { + fieldHBox.getChildren().addAll(countryLabel, countryValueLabel); + detailsVBox.getChildren().add(fieldHBox); + } + } + + private void renderEntityFlowPane(String labelText, List entityDTOs, String entityPageKey) { + FlowPane entityFlowPane = new FlowPane(); + entityFlowPane.setHgap(8); + entityFlowPane.setVgap(8); + entityFlowPane.setAlignment(Pos.CENTER_LEFT); + + Label label = new Label(labelText + ":"); + label.getStyleClass().setAll("general-label-medium-large"); + entityFlowPane.getChildren().add(label); + + if (entityDTOs.isEmpty()) { + Label noEntitiesLabel = new Label("None"); + noEntitiesLabel.getStyleClass().setAll("general-label"); + entityFlowPane.getChildren().add(noEntitiesLabel); + entitiesVBox.getChildren().add(entityFlowPane); + return; + } + + for (int i = 0; i < entityDTOs.size(); i++) { + SmallEntityDTO entityDTO = entityDTOs.get(i); + String nameText = i != entityDTOs.size() - 1 ? entityDTO.getName() + ", " : entityDTO.getName(); + Label entityLabel = new Label(nameText); + entityLabel.getStyleClass().setAll("pseudo-link", "general-label"); + + entityLabel.setOnMouseClicked(event -> { + currentSelectionService.setSelectedId(entityDTO.getId()); + navigationService.switchView(entityPageKey + "?id=" + entityDTO.getId(), true, null); + }); + + entityFlowPane.getChildren().add(entityLabel); + } + + entitiesVBox.getChildren().add(entityFlowPane); + } + + } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierShipmentsController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierShipmentsController.java index da3c7b4..a97d005 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierShipmentsController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SupplierShipmentsController.java @@ -74,7 +74,6 @@ public class SupplierShipmentsController implements DataReceiver statusOptions = Arrays.asList(ShipmentStatus.values()); private long totalRowsCount; private int newShipmentCount = 0; private final List selectedRowsIndices = new ArrayList<>(); @@ -161,6 +160,19 @@ public void setData(SearchData searchData) { this.supplier = searchData.getData(); this.searchMode = searchData.getSearchMode(); + loadComponents(); + + TableConfigurer.configureTableView(tableView, selectRowColumn); + configureTableColumns(); + + setUpListeners(); + loadConfirmDialogs(); + + loadSupplierShipments(searchMode == SearchMode.SECONDARY ? supplier.getId() : null); + } + + // Loading + private void loadComponents() { searchParams.setItemsPerPage(20); SearchOptions searchOptions = SearchOptionsConfiguration.getSearchOptions(Feature.SUPPLIER_ORDER); @@ -168,21 +180,13 @@ public void setData(SearchData searchData) { tableToolbarController = commonViewsLoader.initializeTableToolbar(tableToolbarContainer); tableToolbarController.initialize(new ListHeaderParams (searchMode, searchParams, - "Supplier Shipments", "/img/box-solid.png", Feature.SUPPLIER_ORDER, - searchOptions.getSortOptions(), searchOptions.getFilterOptions(), - () -> loadSupplierShipments(searchMode == SearchMode.SECONDARY ? supplier.getId() : null), null, null)); + "Supplier Shipments", "/img/box-solid.png", Feature.SUPPLIER_ORDER, + searchOptions.getSortOptions(), searchOptions.getFilterOptions(), + () -> loadSupplierShipments(searchMode == SearchMode.SECONDARY ? supplier.getId() : null), null, null)); pageSelectorController = commonViewsLoader.loadPageSelector(pageSelectorContainer); selectComponentLoader.initialize(); - - TableConfigurer.configureTableView(tableView, selectRowColumn); - configureTableColumns(); - setUpListeners(); - loadConfirmDialogs(); - - loadSupplierShipments(searchMode == SearchMode.SECONDARY ? supplier.getId() : null); } - // Loading private void loadConfirmDialogs() { confirmSupplierShipmentCreateController = commonViewsLoader.loadConfirmDialog(confirmCreateDialogContainer); confirmSupplierShipmentCreateController.setActionListener(confirmDialogCreateListener); @@ -394,7 +398,7 @@ private Result> handleShipmentsResponse(Resul tableView.getItems().clear(); if (paginatedResults.results.isEmpty()) { - fallbackManager.setNoResults(true); +// fallbackManager.setNoResults(true); return; } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/dto/SupplierOverviewDTO.java b/src/main/java/org/chainoptim/desktop/features/supplier/dto/SupplierOverviewDTO.java new file mode 100644 index 0000000..0d989c8 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/supplier/dto/SupplierOverviewDTO.java @@ -0,0 +1,18 @@ +package org.chainoptim.desktop.features.supplier.dto; + +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SupplierOverviewDTO { + + private List suppliedComponents; + private List deliveredToFactories; + private List deliveredToWarehouses; +} diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierService.java b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierService.java index d5584f2..7ed07bc 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierService.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierService.java @@ -1,5 +1,6 @@ package org.chainoptim.desktop.features.supplier.service; +import org.chainoptim.desktop.features.supplier.dto.SupplierOverviewDTO; import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; @@ -17,4 +18,5 @@ CompletableFuture>> getSuppliersByOrganization SearchParams searchParams ); CompletableFuture> getSupplierById(Integer supplierId); + CompletableFuture> getSupplierOverview(Integer supplierId); } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierServiceImpl.java index 0426c67..7a03e6a 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierServiceImpl.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.features.supplier.service; import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.features.supplier.dto.SupplierOverviewDTO; import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.shared.caching.CacheKeyBuilder; import org.chainoptim.desktop.shared.caching.CachingService; @@ -73,4 +74,12 @@ public CompletableFuture> getSupplierById(Integer supplierId) { return requestHandler.sendRequest(request, new TypeReference() {}); } + + public CompletableFuture> getSupplierOverview(Integer supplierId) { + String routeAddress = "http://localhost:8080/api/v1/suppliers/" + supplierId.toString() + "/overview"; + + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference() {}); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseController.java b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseController.java index d1f289a..eef6699 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseController.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseController.java @@ -2,38 +2,63 @@ import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationService; +import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.features.warehouse.model.Warehouse; import org.chainoptim.desktop.features.warehouse.service.WarehouseService; +import org.chainoptim.desktop.features.warehouse.service.WarehouseWriteService; +import org.chainoptim.desktop.shared.confirmdialog.controller.GenericConfirmDialogController; +import org.chainoptim.desktop.shared.confirmdialog.controller.RunnableConfirmDialogActionListener; +import org.chainoptim.desktop.shared.confirmdialog.model.ConfirmDialogInput; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.enums.SearchMode; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.SearchData; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Label; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import java.net.URL; -import java.util.Optional; +import java.util.Objects; import java.util.ResourceBundle; +import java.util.function.Consumer; public class WarehouseController implements Initializable { + + // Services private final WarehouseService warehouseService; + private final WarehouseWriteService warehouseWriteService; + private final CommonViewsLoader commonViewsLoader; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; - private final CommonViewsLoader commonViewsLoader; - private final FallbackManager fallbackManager; + // Controllers + private GenericConfirmDialogController confirmWarehouseDeleteController; + + // Listeners + private RunnableConfirmDialogActionListener confirmDialogDeleteListener; + + // State + private final FallbackManager fallbackManager; + private final ToastManager toastManager; private Warehouse warehouse; + // FXML @FXML - private StackPane fallbackContainer; + private Label warehouseName; + @FXML + private Label warehouseLocation; + @FXML + private Button deleteButton; @FXML private TabPane tabPane; @FXML @@ -43,38 +68,58 @@ public class WarehouseController implements Initializable { @FXML private Tab storageTab; @FXML - private Label warehouseName; + private StackPane fallbackContainer; @FXML - private Label warehouseLocation; + private StackPane confirmDeleteDialogContainer; @Inject public WarehouseController(WarehouseService warehouseService, - NavigationService navigationService, - CurrentSelectionService currentSelectionService, - CommonViewsLoader commonViewsLoader, - FallbackManager fallbackManager) { + WarehouseWriteService warehouseWriteService, + CommonViewsLoader commonViewsLoader, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager, + ToastManager toastManager) { this.warehouseService = warehouseService; + this.warehouseWriteService = warehouseWriteService; + this.commonViewsLoader = commonViewsLoader; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; - this.commonViewsLoader = commonViewsLoader; this.fallbackManager = fallbackManager; + this.toastManager = toastManager; } @Override public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); - setupListeners(); + setUpListeners(); + loadDeleteButton(); + loadComponents(); Integer warehouseId = currentSelectionService.getSelectedId(); if (warehouseId != null) { loadWarehouse(warehouseId); } else { - System.out.println("Missing warehouse id."); fallbackManager.setErrorMessage("Failed to load warehouse."); } } - private void setupListeners() { + private void setUpListeners() { + setUpFallbackListeners(); + setUpTabListeners(); + setUpDialogListeners(); + } + + private void setUpFallbackListeners() { + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); + } + + private void setUpTabListeners() { overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { commonViewsLoader.loadTabContent(overviewTab, "/org/chainoptim/desktop/features/warehouse/WarehouseOverviewView.fxml", this.warehouse); @@ -90,15 +135,32 @@ private void setupListeners() { commonViewsLoader.loadTabContent(storageTab, "/org/chainoptim/desktop/features/warehouse/WarehouseStorageView.fxml", this.warehouse); } }); + } - fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { - tabPane.setVisible(newValue); - tabPane.setManaged(newValue); - fallbackContainer.setVisible(!newValue); - fallbackContainer.setManaged(!newValue); - }); + private void setUpDialogListeners() { + Consumer onConfirmDelete = this::handleDeleteWarehouse; + Runnable onCancelDelete = this::closeConfirmDeleteDialog; + confirmDialogDeleteListener = new RunnableConfirmDialogActionListener<>(onConfirmDelete, onCancelDelete); + } + + // Loading + private void loadDeleteButton() { + Image deleteImage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/trash-solid.png"))); + ImageView deleteImageView = new ImageView(deleteImage); + deleteImageView.setFitWidth(14); + deleteImageView.setFitHeight(14); + deleteButton.setGraphic(deleteImageView); + deleteButton.setTooltip(new Tooltip("Delete warehouse")); + deleteButton.setOnAction(event -> openConfirmDeleteDialog(warehouse)); + } + + private void loadComponents() { + confirmWarehouseDeleteController = commonViewsLoader.loadConfirmDialog(confirmDeleteDialogContainer); + confirmWarehouseDeleteController.setActionListener(confirmDialogDeleteListener); + closeConfirmDeleteDialog(); } + private void loadWarehouse(Integer warehouseId) { fallbackManager.reset(); fallbackManager.setLoading(true); @@ -129,9 +191,71 @@ private Result handleWarehouseException(Throwable ex) { return new Result<>(); } + // Actions @FXML private void handleEditWarehouse() { currentSelectionService.setSelectedId(warehouse.getId()); navigationService.switchView("Update-Warehouse?id=" + warehouse.getId(), true, null); } + + private void openConfirmDeleteDialog(Warehouse warehouse) { + ConfirmDialogInput confirmDialogInput = new ConfirmDialogInput( + "Confirm Warehouse Delete", + "Are you sure you want to delete this warehouse?", + null); + confirmWarehouseDeleteController.setData(warehouse, confirmDialogInput); + toggleDialogVisibility(confirmDeleteDialogContainer, true); + } + + private void handleDeleteWarehouse(Warehouse warehouse) { + fallbackManager.setLoading(true); + + warehouseWriteService.deleteWarehouse(warehouse.getId()) + .thenApply(this::handleDeleteResponse) + .exceptionally(this::handleDeleteException); + } + + private Result handleDeleteResponse(Result result) { + Platform.runLater(() -> { + fallbackManager.setLoading(false); + if (result.getError() != null) { + toastManager.addToast(new ToastInfo( + "Failed to delete warehouse.", + "An error occurred while deleting the warehouse.", + OperationOutcome.ERROR) + ); + return; + } + + toastManager.addToast(new ToastInfo( + "Warehouse deleted.", + "The Warehouse \"" + warehouse.getName() + "\" has been successfully deleted.", + OperationOutcome.SUCCESS) + ); + + NavigationServiceImpl.invalidateViewCache("Warehouses"); + navigationService.switchView("Warehouses", true, null); + }); + return result; + } + + private Result handleDeleteException(Throwable ex) { + Platform.runLater(() -> + toastManager.addToast(new ToastInfo( + "Failed to delete warehouse.", + "An error occurred while deleting the warehouse.", + OperationOutcome.ERROR) + ) + ); + return new Result<>(); + } + + private void closeConfirmDeleteDialog() { + toggleDialogVisibility(confirmDeleteDialogContainer, false); + } + + private void toggleDialogVisibility(StackPane dialogContainer, boolean isVisible) { + dialogContainer.setVisible(isVisible); + dialogContainer.setManaged(isVisible); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseOverviewController.java b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseOverviewController.java index 663ae39..bab654f 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseOverviewController.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehouseOverviewController.java @@ -1,20 +1,179 @@ package org.chainoptim.desktop.features.warehouse.controller; -import javafx.fxml.FXML; -import javafx.scene.control.Label; +import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; +import org.chainoptim.desktop.features.warehouse.dto.WarehouseOverviewDTO; import org.chainoptim.desktop.features.warehouse.model.Warehouse; +import org.chainoptim.desktop.features.warehouse.service.WarehouseService; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; import org.chainoptim.desktop.shared.util.DataReceiver; +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.time.format.DateTimeFormatter; +import java.util.List; public class WarehouseOverviewController implements DataReceiver { + + private final WarehouseService warehouseService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + + private final FallbackManager fallbackManager; + private Warehouse warehouse; @FXML - private Label warehouseName; + private VBox detailsVBox; + @FXML + private VBox entitiesVBox; + + @Inject + public WarehouseOverviewController(WarehouseService warehouseService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager) { + this.warehouseService = warehouseService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fallbackManager = fallbackManager; + } @Override public void setData(Warehouse warehouse) { this.warehouse = warehouse; - warehouseName.setText(warehouse.getName()); - System.out.println("Warehouse received in overview: " + warehouse.getName()); + + loadWarehouseOverview(); + } + + private void loadWarehouseOverview() { + if (warehouse == null) { + return; + } + + fallbackManager.reset(); + fallbackManager.setLoading(true); + + warehouseService.getWarehouseOverview(warehouse.getId()) + .thenApply(this::handleOverviewResponse) + .exceptionally(this::handleOverviewException); } + + private Result handleOverviewResponse(Result result) { + Platform.runLater(() -> { + if (result.getError() != null) { + fallbackManager.setErrorMessage("Failed to load warehouse overview"); + return; + } + fallbackManager.setLoading(false); + WarehouseOverviewDTO warehouseOverviewDTO = result.getData(); + + renderUI(warehouseOverviewDTO); + }); + return result; + } + + private Result handleOverviewException(Throwable throwable) { + Platform.runLater(() -> + fallbackManager.setErrorMessage("Failed to load warehouse overview") + ); + return new Result<>(); + } + + private void renderUI(WarehouseOverviewDTO warehouseOverviewDTO) { + renderDates(); + renderLocation(); + renderEntityFlowPane("Compartments", warehouseOverviewDTO.getCompartments(), "Compartment"); + renderEntityFlowPane("Stored Components", warehouseOverviewDTO.getStoredComponents(), "Component"); + renderEntityFlowPane("Stored Products", warehouseOverviewDTO.getStoredProducts(), "Product"); + renderEntityFlowPane("Delivered From Suppliers", warehouseOverviewDTO.getDeliveredFromSuppliers(), "Supplier"); + renderEntityFlowPane("Delivered To Clients", warehouseOverviewDTO.getDeliveredToClients(), "Client"); + } + + private void renderDates() { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy, hh:mm a"); + String formattedCreatedAt = warehouse.getCreatedAt() != null ? warehouse.getCreatedAt().format(dateTimeFormatter) : "None"; + String formattedUpdatedAt = warehouse.getUpdatedAt() != null ? warehouse.getUpdatedAt().format(dateTimeFormatter) : "None"; + renderField("Created At: ", formattedCreatedAt); + renderField("Last Modified: ", formattedUpdatedAt); + } + + private void renderLocation() { + if (warehouse.getLocation() == null) { + return; + } + + Label locationLabel = new Label("• Location"); + locationLabel.getStyleClass().setAll("general-label-large"); + detailsVBox.getChildren().add(locationLabel); + + renderField("Address: ", warehouse.getLocation().getAddress()); + renderField("City: ", warehouse.getLocation().getCity()); + renderField("State: ", warehouse.getLocation().getState()); + renderField("Country: ", warehouse.getLocation().getCountry()); + renderField("Zip Code: ", warehouse.getLocation().getZipCode()); + renderField("Latitude: ", warehouse.getLocation().getLatitude() != null ? warehouse.getLocation().getLatitude().toString() : ""); + renderField("Longitude: ", warehouse.getLocation().getLongitude() != null ? warehouse.getLocation().getLongitude().toString() : ""); + } + + private void renderField(String field, String fieldValue) { + HBox fieldHBox = new HBox(8); + fieldHBox.setAlignment(Pos.CENTER_LEFT); + Label countryLabel = new Label(field); + countryLabel.getStyleClass().setAll("general-label-medium-large"); + + Label countryValueLabel = new Label(); + countryValueLabel.getStyleClass().setAll("general-label"); + countryValueLabel.setText(fieldValue); + + if (fieldValue != null) { + fieldHBox.getChildren().addAll(countryLabel, countryValueLabel); + detailsVBox.getChildren().add(fieldHBox); + } + } + + private void renderEntityFlowPane(String labelText, List entityDTOs, String entityPageKey) { + FlowPane entityFlowPane = new FlowPane(); + entityFlowPane.setHgap(8); + entityFlowPane.setVgap(8); + entityFlowPane.setAlignment(Pos.CENTER_LEFT); + + Label label = new Label(labelText + ":"); + label.getStyleClass().setAll("general-label-medium-large"); + entityFlowPane.getChildren().add(label); + + if (entityDTOs.isEmpty()) { + Label noEntitiesLabel = new Label("None"); + noEntitiesLabel.getStyleClass().setAll("general-label"); + entityFlowPane.getChildren().add(noEntitiesLabel); + entitiesVBox.getChildren().add(entityFlowPane); + return; + } + + for (int i = 0; i < entityDTOs.size(); i++) { + SmallEntityDTO entityDTO = entityDTOs.get(i); + String nameText = i != entityDTOs.size() - 1 ? entityDTO.getName() + ", " : entityDTO.getName(); + Label entityLabel = new Label(nameText); + entityLabel.getStyleClass().setAll("pseudo-link", "general-label"); + + entityLabel.setOnMouseClicked(event -> { + currentSelectionService.setSelectedId(entityDTO.getId()); + navigationService.switchView(entityPageKey + "?id=" + entityDTO.getId(), true, null); + }); + + entityFlowPane.getChildren().add(entityLabel); + } + + entitiesVBox.getChildren().add(entityFlowPane); + } + + } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/dto/WarehouseOverviewDTO.java b/src/main/java/org/chainoptim/desktop/features/warehouse/dto/WarehouseOverviewDTO.java new file mode 100644 index 0000000..da5bfa2 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/dto/WarehouseOverviewDTO.java @@ -0,0 +1,20 @@ +package org.chainoptim.desktop.features.warehouse.dto; + +import org.chainoptim.desktop.shared.search.dto.SmallEntityDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WarehouseOverviewDTO { + + private List compartments; + private List storedComponents; + private List storedProducts; + private List deliveredFromSuppliers; + private List deliveredToClients; +} diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseService.java b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseService.java index 8d17fb9..d27e3ac 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseService.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseService.java @@ -1,5 +1,6 @@ package org.chainoptim.desktop.features.warehouse.service; +import org.chainoptim.desktop.features.warehouse.dto.WarehouseOverviewDTO; import org.chainoptim.desktop.features.warehouse.model.Warehouse; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; @@ -17,4 +18,5 @@ CompletableFuture>> getWarehousesByOrganizati SearchParams searchParams ); CompletableFuture> getWarehouseById(Integer warehouseId); + CompletableFuture> getWarehouseOverview(Integer warehouseId); } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseServiceImpl.java index d1e8333..d16e841 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseServiceImpl.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.features.warehouse.service; import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.features.warehouse.dto.WarehouseOverviewDTO; import org.chainoptim.desktop.features.warehouse.model.Warehouse; import org.chainoptim.desktop.shared.caching.CacheKeyBuilder; import org.chainoptim.desktop.shared.caching.CachingService; @@ -72,4 +73,12 @@ public CompletableFuture> getWarehouseById(Integer warehouseId return requestHandler.sendRequest(request, new TypeReference() {}); } + + public CompletableFuture> getWarehouseOverview(Integer warehouseId) { + String routeAddress = "http://localhost:8080/api/v1/warehouses/" + warehouseId.toString() + "/overview"; + + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference() {}); + } } diff --git a/src/main/resources/org/chainoptim/desktop/features/client/ClientOverviewView.fxml b/src/main/resources/org/chainoptim/desktop/features/client/ClientOverviewView.fxml index 019c253..c5cb59c 100644 --- a/src/main/resources/org/chainoptim/desktop/features/client/ClientOverviewView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/client/ClientOverviewView.fxml @@ -1,11 +1,14 @@ - - + fx:controller="org.chainoptim.desktop.features.client.controller.ClientOverviewController" + style="-fx-padding: 30px;" minHeight="600" spacing="20"> + + - + + + diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/FactoryOverviewView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/FactoryOverviewView.fxml index 8302545..ba582e6 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/FactoryOverviewView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/FactoryOverviewView.fxml @@ -1,11 +1,14 @@ - - + style="-fx-padding: 30px;" minHeight="600" spacing="20"> - + + + + + + diff --git a/src/main/resources/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml b/src/main/resources/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml index 22fff89..b669c33 100644 --- a/src/main/resources/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml @@ -1,10 +1,14 @@ - + fx:controller="org.chainoptim.desktop.features.supplier.controller.SupplierOverviewController" + style="-fx-padding: 30px;" minHeight="600" spacing="20"> -