diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index d412e615..356254a4 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -41,13 +41,12 @@ // - User opens org.chainoptim.desktop.core.user.controller to javafx.fxml, com.google.guice; - opens org.chainoptim.desktop.core.user.repository to com.google.guice; opens org.chainoptim.desktop.core.user.service to com.google.guice; opens org.chainoptim.desktop.core.user.model to com.fasterxml.jackson.databind; // - Organization opens org.chainoptim.desktop.core.organization.controller to javafx.fxml, com.google.guice; - opens org.chainoptim.desktop.core.organization.repository to com.google.guice; + opens org.chainoptim.desktop.core.organization.service to com.google.guice; opens org.chainoptim.desktop.core.organization.model to com.fasterxml.jackson.databind; // Features @@ -100,6 +99,9 @@ opens org.chainoptim.desktop.features.scanalysis.factorygraph.model to com.fasterxml.jackson.databind, java.base; opens org.chainoptim.desktop.features.scanalysis.factorygraph.service to com.google.guice, javafx.web; + // - Settings + opens org.chainoptim.desktop.features.settings.controller to javafx.fxml, com.google.guice; + // Shared // - Common UI elements opens org.chainoptim.desktop.shared.common.uielements to javafx.fxml, com.google.guice; diff --git a/src/main/java/org/chainoptim/desktop/AppModule.java b/src/main/java/org/chainoptim/desktop/AppModule.java index 0d24788c..c0608049 100644 --- a/src/main/java/org/chainoptim/desktop/AppModule.java +++ b/src/main/java/org/chainoptim/desktop/AppModule.java @@ -1,7 +1,5 @@ package org.chainoptim.desktop; -import com.google.inject.AbstractModule; -import com.google.inject.Singleton; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.abstraction.GuiceControllerFactory; import org.chainoptim.desktop.core.abstraction.JavaFXThreadRunner; @@ -9,8 +7,10 @@ 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.core.user.repository.UserRepository; -import org.chainoptim.desktop.core.user.repository.UserRepositoryImpl; +import org.chainoptim.desktop.core.organization.service.OrganizationService; +import org.chainoptim.desktop.core.organization.service.OrganizationServiceImpl; +import org.chainoptim.desktop.core.user.service.UserService; +import org.chainoptim.desktop.core.user.service.UserServiceImpl; import org.chainoptim.desktop.core.user.service.AuthenticationService; import org.chainoptim.desktop.core.user.service.AuthenticationServiceImpl; import org.chainoptim.desktop.features.client.service.*; @@ -38,10 +38,12 @@ import org.chainoptim.desktop.shared.features.location.service.LocationService; import org.chainoptim.desktop.shared.features.location.service.LocationServiceImpl; import org.chainoptim.desktop.shared.search.model.SearchParams; -import org.chainoptim.desktop.shared.util.JsonUtil; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderServiceImpl; +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; + import java.net.http.HttpClient; /* @@ -66,7 +68,8 @@ protected void configure() { bind(NavigationService.class).to(NavigationServiceImpl.class); bind(FXMLLoaderService.class).to(FXMLLoaderServiceImpl.class); bind(AuthenticationService.class).to(AuthenticationServiceImpl.class); - bind(UserRepository.class).to(UserRepositoryImpl.class); + bind(UserService.class).to(UserServiceImpl.class); + bind(OrganizationService.class).to(OrganizationServiceImpl.class); // Features // - Product diff --git a/src/main/java/org/chainoptim/desktop/core/main/controller/HeaderController.java b/src/main/java/org/chainoptim/desktop/core/main/controller/ListHeaderController.java similarity index 56% rename from src/main/java/org/chainoptim/desktop/core/main/controller/HeaderController.java rename to src/main/java/org/chainoptim/desktop/core/main/controller/ListHeaderController.java index 30ef8e5b..bf7d09f9 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/controller/HeaderController.java +++ b/src/main/java/org/chainoptim/desktop/core/main/controller/ListHeaderController.java @@ -1,11 +1,15 @@ package org.chainoptim.desktop.core.main.controller; import com.google.inject.Inject; +import javafx.animation.Animation; +import javafx.animation.Interpolator; +import javafx.animation.RotateTransition; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.effect.ColorAdjust; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.util.Duration; import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.shared.search.model.SearchParams; @@ -14,7 +18,7 @@ import java.util.Map; import java.util.Objects; -public class HeaderController { +public class ListHeaderController { private final SearchParams searchParams; private final NavigationService navigationService; @@ -25,23 +29,25 @@ public class HeaderController { @FXML private TextField searchBar; @FXML - private ComboBox sortOptions; + private Button searchButton; + @FXML + private ComboBox sortOptions; @FXML private Button orderingButton; @FXML - private Button createNewItemButton; + private Button refreshButton; @FXML - private Button searchButton; + private Button createNewItemButton; private Map sortOptionsMap; - private final Image sortUpIcon = new Image(getClass().getResourceAsStream("/img/sort-up.png")); - private final Image sortDownIcon = new Image(getClass().getResourceAsStream("/img/sort-down.png")); + private final Image sortUpIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/sort-up.png"))); + private final Image sortDownIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/sort-down.png"))); private final ImageView sortUpImageView = new ImageView(sortUpIcon); private final ImageView sortDownImageView = new ImageView(sortDownIcon); @Inject - public HeaderController( + public ListHeaderController( SearchParams searchParams, NavigationService navigationService ) { @@ -49,21 +55,41 @@ public HeaderController( this.navigationService = navigationService; } - public void initializeHeader(String titleText, String titleIconPath, Map sortOptionsMap, String createNewItemButtonText, String createNewItem) { + public void initializeHeader(String titleText, String titleIconPath, + Map sortOptionsMap, + Runnable refreshAction, + String createNewItemButtonText, String createNewItem) { this.sortOptionsMap = sortOptionsMap; - setSearchButton(); setTitle(titleText, titleIconPath); + setSearchButton(); setOrderingButton(); setSortOptions(new ArrayList<>(sortOptionsMap.values())); + setRefreshButton(refreshAction); setCreateNewItemButton(createNewItemButtonText); setNewItemKey(createNewItem); } + private void setTitle(String titleText, String titleIconPath) { + title.setText(titleText); + Image titleIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream(titleIconPath))); + ImageView titleIconView = new ImageView(titleIcon); + titleIconView.setFitWidth(18); + titleIconView.setFitHeight(18); + ColorAdjust colorAdjust = new ColorAdjust(); + colorAdjust.setBrightness(-1); + titleIconView.setEffect(colorAdjust); + title.setGraphic(titleIconView); + title.setContentDisplay(ContentDisplay.LEFT); + } + private void setSearchButton() { - Image searchIcon = new Image(getClass().getResourceAsStream("/img/search.png")); + Image searchIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/search.png"))); ImageView searchIconView = new ImageView(searchIcon); - searchIconView.setFitWidth(17); - searchIconView.setFitHeight(17); + searchIconView.setFitWidth(16); + searchIconView.setFitHeight(16); + ColorAdjust colorAdjust = new ColorAdjust(); + colorAdjust.setBrightness(1); + searchIconView.setEffect(colorAdjust); searchButton.setGraphic(searchIconView); } @@ -72,30 +98,38 @@ private void setSortOptions(List sortOptions) { } private void setOrderingButton() { - sortDownImageView.setFitWidth(17); - sortDownImageView.setFitHeight(17); - sortUpImageView.setFitWidth(17); - sortUpImageView.setFitHeight(17); + sortDownImageView.setFitWidth(16); + sortDownImageView.setFitHeight(16); + sortUpImageView.setFitWidth(16); + sortUpImageView.setFitHeight(16); orderingButton.setGraphic(sortUpImageView); } - private void setTitle(String titleText, String titleIconPath) { - title.setText(titleText); - Image titleIcon = new Image(getClass().getResourceAsStream(titleIconPath)); - ImageView titleIconView = new ImageView(titleIcon); - titleIconView.setFitWidth(21); - titleIconView.setFitHeight(21); - ColorAdjust colorAdjust = new ColorAdjust(); - colorAdjust.setBrightness(-1); - titleIconView.setEffect(colorAdjust); - title.setGraphic(titleIconView); - title.setContentDisplay(ContentDisplay.LEFT); - + public void setRefreshButton(Runnable refreshAction) { + Image refreshIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/rotate-right-solid.png"))); + ImageView refreshIconView = new ImageView(refreshIcon); + refreshIconView.setFitWidth(14); + refreshIconView.setFitHeight(14); + + // Apply rotation on click + RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), refreshIconView); + rotateTransition.setByAngle(360); + rotateTransition.setCycleCount(1); + rotateTransition.setInterpolator(Interpolator.LINEAR); + + refreshButton.setGraphic(refreshIconView); + + refreshButton.setOnAction(e -> { + rotateTransition.stop(); + rotateTransition.playFromStart(); + // Run refresh action + refreshAction.run(); + }); } private void setCreateNewItemButton(String text) { createNewItemButton.setText("Create New " + text); - Image plusIcon = new Image(getClass().getResourceAsStream("/img/plus.png")); + Image plusIcon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/plus.png"))); ImageView plusIconView = new ImageView(plusIcon); plusIconView.setFitWidth(12); plusIconView.setFitHeight(12); @@ -109,7 +143,6 @@ private void setCreateNewItemButton(String text) { @FXML private void handleSearch() { searchParams.setSearchQuery(searchBar.getText()); - System.out.println(searchParams.getSearchQuery()); } @FXML @@ -124,14 +157,13 @@ private void handleOrdering() { @FXML private void handleSortOption() { - String selectedFilter = sortOptions.getValue().toString(); - String backendFilter = sortOptionsMap.entrySet().stream() - .filter(entry -> Objects.equals(entry.getValue(), selectedFilter)) + String selectedSortOption = sortOptions.getValue(); + String backendSortOption = sortOptionsMap.entrySet().stream() + .filter(entry -> Objects.equals(entry.getValue(), selectedSortOption)) .map(Map.Entry::getKey) .findFirst() - .orElse(selectedFilter); - searchParams.setSortOption(backendFilter); - System.out.println(searchParams.getSortOption()); + .orElse(selectedSortOption); + searchParams.setSortOption(backendSortOption); } private void setNewItemKey(String createNewItem) { @@ -140,7 +172,7 @@ private void setNewItemKey(String createNewItem) { @FXML private void handleCreateNewItem() { - navigationService.switchView(createNewItem); + navigationService.switchView(createNewItem, true); } } diff --git a/src/main/java/org/chainoptim/desktop/core/main/controller/OverviewController.java b/src/main/java/org/chainoptim/desktop/core/main/controller/OverviewController.java index a6c2d9d0..b39d1c16 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/controller/OverviewController.java +++ b/src/main/java/org/chainoptim/desktop/core/main/controller/OverviewController.java @@ -5,9 +5,8 @@ import javafx.fxml.Initializable; import org.chainoptim.desktop.core.context.TenantContext; import org.chainoptim.desktop.core.user.model.User; -import org.chainoptim.desktop.core.user.repository.UserRepository; +import org.chainoptim.desktop.core.user.service.UserService; import org.chainoptim.desktop.core.user.service.AuthenticationService; -import org.chainoptim.desktop.core.user.service.AuthenticationServiceImpl; import org.chainoptim.desktop.core.user.util.TokenManager; import java.io.UnsupportedEncodingException; @@ -17,12 +16,12 @@ public class OverviewController implements Initializable { private final AuthenticationService authenticationService; - private final UserRepository userRepository; + private final UserService userService; @Inject - public OverviewController(AuthenticationService authenticationService, UserRepository userRepository) { + public OverviewController(AuthenticationService authenticationService, UserService userService) { this.authenticationService = authenticationService; - this.userRepository = userRepository; + this.userService = userService; } @Override @@ -38,7 +37,7 @@ public void initialize(URL location, ResourceBundle resources) { private void fetchAndSetUser(String username) { try { - userRepository.getUserByUsername(username) + userService.getUserByUsername(username) .thenAcceptAsync(userOptional -> userOptional.ifPresentOrElse(this::updateCurrentUser, () -> Platform.runLater(() -> System.err.println("User not found.")))) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/core/main/controller/SidebarController.java b/src/main/java/org/chainoptim/desktop/core/main/controller/SidebarController.java index 529a1e1d..3b5ee74a 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/controller/SidebarController.java +++ b/src/main/java/org/chainoptim/desktop/core/main/controller/SidebarController.java @@ -46,25 +46,30 @@ public SidebarController(NavigationService navigationService, AuthenticationServ private VBox buttonContainer; private final List navigationButtons = new ArrayList<>(); + private static final String ICONS_PATH = "/img/"; @FXML public Button toggleButton; -// @FXML -// public Button accountButton; + @FXML + public Button backButton; @FXML public Button logoutButton; // Configuration - private final List orderedKeys = List.of("Overview", "Organization", "Products", "Factories", "Warehouses", "Suppliers", "Clients"); - private final Map buttonIconMap = Map.of( - "Overview", "globe-solid.png", - "Organization", "building-regular.png", - "Products", "box-solid.png", - "Factories", "industry-solid.png", - "Warehouses", "warehouse-solid.png", - "Suppliers", "truck-arrow-right-solid.png", - "Clients", "universal-access-solid.png", - "Account", "user-solid.png", - "Toggle", "bars-solid.png" + private final List orderedKeys = List.of("Overview", "Organization", "Products", "Factories", "Warehouses", "Suppliers", "Clients", "Settings"); + private final Map buttonIconMap = Map.ofEntries( + Map.entry("Overview", "globe-solid.png"), + Map.entry("Organization", "building-solid.png"), + Map.entry("Products", "box-solid.png"), + Map.entry("Factories", "industry-solid.png"), + Map.entry("Warehouses", "warehouse-solid.png"), + Map.entry("Suppliers", "truck-arrow-right-solid.png"), + Map.entry("Clients", "universal-access-solid.png"), + Map.entry("Settings", "gear-solid.png"), + + Map.entry("Account", "user-solid.png"), + Map.entry("Back", "arrow-left-solid.png"), + Map.entry("Toggle", "bars-solid.png"), + Map.entry("Logout", "right-from-bracket-solid.png") ); private static final double COLLAPSED_WIDTH = 52; private static final double EXPANDED_WIDTH = 256; @@ -75,20 +80,22 @@ public SidebarController(NavigationService navigationService, AuthenticationServ public void initialize() { initializeNavigationButtons(); createSidebarButtons(); - setButtonGraphic(logoutButton, "/img/right-from-bracket-solid.png"); // Navigate to Overview - navigationService.switchView("Overview"); + navigationService.switchView("Overview", true); - // Toggle button - setButtonGraphic(toggleButton, "/img/" + buttonIconMap.get("Toggle")); + // Back, Toggle and Logout buttons + setButtonGraphic(backButton, ICONS_PATH + buttonIconMap.get("Back")); + backButton.setOnAction(e -> navigationService.goBack()); + setButtonGraphic(toggleButton, ICONS_PATH + buttonIconMap.get("Toggle")); toggleButton.setOnAction(e -> toggleSidebar()); + setButtonGraphic(logoutButton, ICONS_PATH + buttonIconMap.get("Logout")); } private void initializeNavigationButtons() { orderedKeys.forEach(key -> { - String iconPath = "/img/" + buttonIconMap.get(key); - Runnable action = () -> navigationService.switchView(key); + String iconPath = ICONS_PATH + buttonIconMap.get(key); + Runnable action = () -> navigationService.switchView(key, true); navigationButtons.add(new SidebarButton(key, iconPath, action)); }); } diff --git a/src/main/java/org/chainoptim/desktop/core/main/service/NavigationService.java b/src/main/java/org/chainoptim/desktop/core/main/service/NavigationService.java index 2c048a04..3042cdbf 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/service/NavigationService.java +++ b/src/main/java/org/chainoptim/desktop/core/main/service/NavigationService.java @@ -4,7 +4,9 @@ public interface NavigationService { - void switchView(String viewKey); + void switchView(String viewKey, boolean forward); void setMainContentArea(StackPane contentArea); + + void goBack(); } diff --git a/src/main/java/org/chainoptim/desktop/core/main/service/NavigationServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/main/service/NavigationServiceImpl.java index 2db35c5a..03475b66 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/service/NavigationServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/main/service/NavigationServiceImpl.java @@ -11,16 +11,14 @@ import lombok.Getter; import lombok.Setter; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /* - * Service responsible for handling app navigation from SidebarController + * Service responsible for handling app navigation throughout the app * Loads views on demand and caches them, including dynamic routes - * + * Also records navigation history for back navigation */ public class NavigationServiceImpl implements NavigationService { @@ -44,51 +42,60 @@ public NavigationServiceImpl(FXMLLoaderService fxmlLoaderService, private StackPane mainContentArea; private String currentViewKey; + private List previousViewKeys; @Getter private static final Map viewCache = new HashMap<>(); private final Map viewMap = Map.ofEntries( - // Main pages Map.entry("Overview", "/org/chainoptim/desktop/core/main/OverviewView.fxml"), Map.entry("Organization", "/org/chainoptim/desktop/core/organization/OrganizationView.fxml"), + Map.entry("Products", "/org/chainoptim/desktop/features/product/ProductsView.fxml"), - Map.entry("Factories", "/org/chainoptim/desktop/features/factory/FactoriesView.fxml"), - Map.entry("Warehouses", "/org/chainoptim/desktop/features/warehouse/WarehousesView.fxml"), - Map.entry("Suppliers", "/org/chainoptim/desktop/features/supplier/SuppliersView.fxml"), - Map.entry("Clients", "/org/chainoptim/desktop/features/client/ClientsView.fxml"), - // Dynamic route pages Map.entry("Product", "/org/chainoptim/desktop/features/product/ProductView.fxml"), - Map.entry("Factory", "/org/chainoptim/desktop/features/factory/FactoryView.fxml"), - Map.entry("Warehouse", "/org/chainoptim/desktop/features/warehouse/WarehouseView.fxml"), - Map.entry("Supplier", "/org/chainoptim/desktop/features/supplier/SupplierView.fxml"), - Map.entry("Client", "/org/chainoptim/desktop/features/client/ClientView.fxml"), - - // Create forms Map.entry("Create-Product", "/org/chainoptim/desktop/features/product/CreateProductView.fxml"), + + Map.entry("Factories", "/org/chainoptim/desktop/features/factory/FactoriesView.fxml"), + Map.entry("Factory", "/org/chainoptim/desktop/features/factory/FactoryView.fxml"), Map.entry("Create-Factory", "/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml"), + Map.entry("Update-Factory", "/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml"), + + Map.entry("Warehouses", "/org/chainoptim/desktop/features/warehouse/WarehousesView.fxml"), + Map.entry("Warehouse", "/org/chainoptim/desktop/features/warehouse/WarehouseView.fxml"), Map.entry("Create-Warehouse", "/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml"), + Map.entry("Update-Warehouse", "/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml"), + + Map.entry("Suppliers", "/org/chainoptim/desktop/features/supplier/SuppliersView.fxml"), + Map.entry("Supplier", "/org/chainoptim/desktop/features/supplier/SupplierView.fxml"), Map.entry("Create-Supplier", "/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml"), + Map.entry("Update-Supplier", "/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml"), + + Map.entry("Clients", "/org/chainoptim/desktop/features/client/ClientsView.fxml"), + Map.entry("Client", "/org/chainoptim/desktop/features/client/ClientView.fxml"), Map.entry("Create-Client", "/org/chainoptim/desktop/features/client/CreateClientView.fxml"), - Map.entry("Create-Stage", "/org/chainoptim/desktop/features/client/CreateFactoryStageView.fxml") + Map.entry("Update-Client", "/org/chainoptim/desktop/features/client/UpdateClientView.fxml"), + + Map.entry("Create-Stage", "/org/chainoptim/desktop/features/client/CreateFactoryStageView.fxml"), + + Map.entry("Settings", "/org/chainoptim/desktop/features/settings/SettingsView.fxml") ); - public void switchView(String viewKey) { + public void switchView(String viewKey, boolean forward) { // Skip if already there if (Objects.equals(currentViewKey, viewKey)) { return; } - System.out.println(viewCache); + + // Reset fallback state between pages + fallbackManager.reset(); // Get view from cache or load it Node view = viewCache.computeIfAbsent(viewKey, this::loadView); // Display view if (view != null) { - threadRunner.runLater(() -> { - mainContentArea.getChildren().setAll(view); - fallbackManager.reset(); - }); + threadRunner.runLater(() -> mainContentArea.getChildren().setAll(view)); + handleHistory(forward); currentViewKey = viewKey; } } @@ -126,7 +133,33 @@ private String findBaseKey(String viewKey) { return baseViewKey; } + private void handleHistory(boolean forward) { + // Add to history if forward and remove last otherwise + if (forward) { + if (previousViewKeys == null) { + previousViewKeys = new ArrayList<>(); + } + if (currentViewKey != null) { + previousViewKeys.add(currentViewKey); + } + } else { + if (previousViewKeys != null && !previousViewKeys.isEmpty()) { + previousViewKeys.removeLast(); + } + } + } + + public void goBack() { + if (previousViewKeys != null && !previousViewKeys.isEmpty()) { + switchView(previousViewKeys.getLast(), false); + } + } + public static void invalidateViewCache() { viewCache.clear(); } + + public static void invalidateViewCache(String viewKey) { + viewCache.remove(viewKey); + } } diff --git a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationController.java b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationController.java index 0cdefd6e..84ae6597 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationController.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationController.java @@ -1,4 +1,102 @@ package org.chainoptim.desktop.core.organization.controller; -public class OrganizationController { +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.effect.ColorAdjust; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.chainoptim.desktop.core.context.TenantContext; +import org.chainoptim.desktop.core.organization.model.Organization; +import org.chainoptim.desktop.core.organization.service.OrganizationService; +import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.features.factory.model.Factory; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.search.model.PaginatedResults; + +import java.net.URL; +import java.util.Objects; +import java.util.Optional; +import java.util.ResourceBundle; + +public class OrganizationController implements Initializable { + + private final OrganizationService organizationService; + + private final FallbackManager fallbackManager; + + private Organization organization; + + @FXML + private Label organizationName; + @FXML + private Label organizationAddress; + @FXML + private Label planLabel; + @FXML + private VBox usersVBox; + + @Inject + public OrganizationController(OrganizationService organizationService, + FallbackManager fallbackManager) { + this.organizationService = organizationService; + this.fallbackManager = fallbackManager; + } + + @Override + public void initialize(URL location, ResourceBundle resourceBundle) { + User currentUser = TenantContext.getCurrentUser(); + if (currentUser == null) { + Platform.runLater(() -> fallbackManager.setLoading(false)); + return; + } + + organizationService.getOrganizationById(currentUser.getOrganization().getId(), true) + .thenApply(this::handleOrganizationResponse) + .exceptionally(this::handleOrganizationException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + + private Optional handleOrganizationResponse(Optional organizationOptional) { + Platform.runLater(() -> { + if (organizationOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to load organization."); + return; + } + usersVBox.getChildren().clear(); + organization = organizationOptional.get(); + + initializeUI(); + }); + return organizationOptional; + } + + private void initializeUI() { + organizationName.setText("Organization: " + organization.getName()); + organizationAddress.setText("Address: " + organization.getAddress()); + planLabel.setText("Subscription Plan: " + organization.getSubscriptionPlan().name()); + System.out.println("Organization: " + organization); + if (organization.getUsers() == null) { + return; + } + + for (User user : organization.getUsers()) { + Label usernameLabel = new Label(user.getUsername()); + usersVBox.getChildren().add(usernameLabel); + } + } + + private Optional handleOrganizationException(Throwable ex) { + Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load organization.")); + return Optional.empty(); + } + + @FXML + private void handleChangePlan() { + System.out.println("Changing plan"); + } } diff --git a/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepository.java b/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepository.java deleted file mode 100644 index 7283c07c..00000000 --- a/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.chainoptim.desktop.core.organization.repository; - -import org.chainoptim.desktop.core.organization.model.Organization; - -import java.util.Optional; - -public interface OrganizationRepository { - public Optional getOrganizationById(Integer organizationId); -} diff --git a/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepositoryImpl.java b/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepositoryImpl.java deleted file mode 100644 index a150f75c..00000000 --- a/src/main/java/org/chainoptim/desktop/core/organization/repository/OrganizationRepositoryImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.chainoptim.desktop.core.organization.repository; - -import org.chainoptim.desktop.core.organization.model.Organization; -import org.chainoptim.desktop.shared.util.JsonUtil; - -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Optional; - -public class OrganizationRepositoryImpl implements OrganizationRepository { - - private final HttpClient client = HttpClient.newHttpClient(); - - public Optional getOrganizationById(Integer organizationId) { - String routeAddress = "http://localhost:8080/api/v1/organizations/" + organizationId.toString(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .GET() - .build(); - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() == HttpURLConnection.HTTP_OK) { - String responseBody = response.body(); - Organization organization = JsonUtil.getObjectMapper().readValue(responseBody, Organization.class); - return Optional.of(organization); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - return Optional.empty(); - } -} diff --git a/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationService.java b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationService.java index f769cd3d..612f4015 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationService.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationService.java @@ -1,4 +1,11 @@ package org.chainoptim.desktop.core.organization.service; -public class OrganizationService { +import org.chainoptim.desktop.core.organization.model.Organization; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public interface OrganizationService { + + CompletableFuture> getOrganizationById(Integer organizationId, boolean includeUsers); } diff --git a/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java new file mode 100644 index 00000000..383f18cf --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java @@ -0,0 +1,48 @@ +package org.chainoptim.desktop.core.organization.service; + +import org.chainoptim.desktop.core.organization.model.Organization; +import org.chainoptim.desktop.core.user.util.TokenManager; +import org.chainoptim.desktop.shared.util.JsonUtil; + +import com.fasterxml.jackson.core.type.TypeReference; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class OrganizationServiceImpl implements OrganizationService { + + private final HttpClient client = HttpClient.newHttpClient(); + + private static final String HEADER_KEY = "Authorization"; + private static final String HEADER_VALUE_PREFIX = "Bearer "; + + public CompletableFuture> getOrganizationById(Integer organizationId, boolean includeUsers) { + String routeAddress = "http://localhost:8080/api/v1/organizations/" + organizationId.toString() + "?includeUsers=" + includeUsers; + + String jwtToken = TokenManager.getToken(); + if (jwtToken == null) return new CompletableFuture<>(); + String headerValue = HEADER_VALUE_PREFIX + jwtToken; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(routeAddress)) + .GET() + .headers(HEADER_KEY, headerValue) + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> { + if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); + try { + Organization organization = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference() {}); + return Optional.of(organization); + } catch (Exception e) { + e.printStackTrace(); + return Optional.empty(); + } + }); + } +} diff --git a/src/main/java/org/chainoptim/desktop/core/user/repository/UserRepository.java b/src/main/java/org/chainoptim/desktop/core/user/repository/UserRepository.java deleted file mode 100644 index 69ad1567..00000000 --- a/src/main/java/org/chainoptim/desktop/core/user/repository/UserRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.chainoptim.desktop.core.user.repository; - -import org.chainoptim.desktop.core.user.model.User; - -import java.io.UnsupportedEncodingException; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -public interface UserRepository { - CompletableFuture> getUserByUsername(String username) throws UnsupportedEncodingException; -} diff --git a/src/main/java/org/chainoptim/desktop/core/user/service/UserService.java b/src/main/java/org/chainoptim/desktop/core/user/service/UserService.java index fc5d3267..aad71b4b 100644 --- a/src/main/java/org/chainoptim/desktop/core/user/service/UserService.java +++ b/src/main/java/org/chainoptim/desktop/core/user/service/UserService.java @@ -1,5 +1,11 @@ package org.chainoptim.desktop.core.user.service; -public class UserService { +import org.chainoptim.desktop.core.user.model.User; +import java.io.UnsupportedEncodingException; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public interface UserService { + CompletableFuture> getUserByUsername(String username) throws UnsupportedEncodingException; } diff --git a/src/main/java/org/chainoptim/desktop/core/user/repository/UserRepositoryImpl.java b/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java similarity index 94% rename from src/main/java/org/chainoptim/desktop/core/user/repository/UserRepositoryImpl.java rename to src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java index 10e149bc..57bf91a7 100644 --- a/src/main/java/org/chainoptim/desktop/core/user/repository/UserRepositoryImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java @@ -1,4 +1,4 @@ -package org.chainoptim.desktop.core.user.repository; +package org.chainoptim.desktop.core.user.service; import org.chainoptim.desktop.core.user.model.User; import org.chainoptim.desktop.shared.util.JsonUtil; @@ -15,7 +15,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; -public class UserRepositoryImpl implements UserRepository { +public class UserServiceImpl implements UserService { private final HttpClient client = HttpClient.newHttpClient(); diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientController.java index 04816783..737163b7 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientController.java @@ -13,6 +13,7 @@ import org.chainoptim.desktop.MainApplication; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.features.client.model.Client; import org.chainoptim.desktop.features.client.service.ClientService; import org.chainoptim.desktop.shared.fallback.FallbackManager; @@ -27,6 +28,7 @@ public class ClientController implements Initializable { private final ClientService clientService; + private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final FXMLLoaderService fxmlLoaderService; private final ControllerFactory controllerFactory; @@ -53,11 +55,13 @@ public class ClientController implements Initializable { @Inject public ClientController(ClientService clientService, + NavigationService navigationService, CurrentSelectionService currentSelectionService, FXMLLoaderService fxmlLoaderService, ControllerFactory controllerFactory, FallbackManager fallbackManager) { this.clientService = clientService; + this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.fxmlLoaderService = fxmlLoaderService; this.controllerFactory = controllerFactory; @@ -66,14 +70,55 @@ public ClientController(ClientService clientService, @Override public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + setupListeners(); + Integer clientId = currentSelectionService.getSelectedId(); - if (clientId == null) { + if (clientId != null) { + loadClient(clientId); + } else { System.out.println("Missing client id."); fallbackManager.setErrorMessage("Failed to load client."); } - loadClient(clientId); - System.out.println("Client initialized"); - setupTabListeners(); + } + + private void loadFallbackManager() { + // Load view into fallbackContainer + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void setupListeners() { + overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { + loadTabContent(overviewTab, "/org/chainoptim/desktop/features/client/ClientOverviewView.fxml", this.client); + } + }); + ordersTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && ordersTab.getContent() == null) { + loadTabContent(ordersTab, "/org/chainoptim/desktop/features/client/ClientOrdersView.fxml", this.client); + } + }); + shipmentsTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && shipmentsTab.getContent() == null) { + loadTabContent(shipmentsTab, "/org/chainoptim/desktop/features/client/ClientShipmentsView.fxml", this.client); + } + }); + evaluationTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && evaluationTab.getContent() == null) { + loadTabContent(evaluationTab, "/org/chainoptim/desktop/features/client/ClientEvaluationView.fxml", this.client); + } + }); + + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadClient(Integer clientId) { @@ -111,29 +156,6 @@ private Optional handleClientException(Throwable ex) { return Optional.empty(); } - private void setupTabListeners() { - overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { - loadTabContent(overviewTab, "/org/chainoptim/desktop/features/client/ClientOverviewView.fxml", this.client); - } - }); - ordersTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && ordersTab.getContent() == null) { - loadTabContent(ordersTab, "/org/chainoptim/desktop/features/client/ClientOrdersView.fxml", this.client); - } - }); - shipmentsTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && shipmentsTab.getContent() == null) { - loadTabContent(shipmentsTab, "/org/chainoptim/desktop/features/client/ClientShipmentsView.fxml", this.client); - } - }); - evaluationTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && evaluationTab.getContent() == null) { - loadTabContent(evaluationTab, "/org/chainoptim/desktop/features/client/ClientEvaluationView.fxml", this.client); - } - }); - } - private void loadTabContent(Tab tab, String fxmlFilepath, Client client) { try { FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlFilepath)); @@ -148,6 +170,9 @@ private void loadTabContent(Tab tab, String fxmlFilepath, Client client) { } @FXML - private void handleEditClient() {System.out.println("Edit Client Working");} + private void handleEditClient() { + currentSelectionService.setSelectedId(client.getId()); + navigationService.switchView("Update-Client?id=" + client.getId(), true); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientsController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientsController.java index 7d3aa2e2..2ec5e4fa 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/ClientsController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/ClientsController.java @@ -8,11 +8,12 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; -import org.chainoptim.desktop.core.main.controller.HeaderController; +import org.chainoptim.desktop.core.main.controller.ListHeaderController; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.core.user.model.User; @@ -41,17 +42,19 @@ public class ClientsController implements Initializable { private final SearchParams searchParams; @FXML - private HeaderController headerController; + private ListHeaderController headerController; @FXML private PageSelectorController pageSelectorController; @FXML - private StackPane pageSelectorContainer; + private ScrollPane clientsScrollPane; @FXML - private StackPane fallbackContainer; + private VBox clientsVBox; @FXML private StackPane headerContainer; @FXML - private VBox clientsVBox; + private StackPane fallbackContainer; + @FXML + private StackPane pageSelectorContainer; private long totalCount; @@ -82,36 +85,21 @@ public ClientsController(ClientService clientService, public void initialize(URL location, ResourceBundle resources) { initializeHeader(); loadFallbackManager(); - loadClients(); setUpListeners(); + loadClients(); initializePageSelector(); } - private void initializePageSelector() { - FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", - controllerFactory::createController - ); - try { - Node pageSelectorView = loader.load(); - pageSelectorContainer.getChildren().add(pageSelectorView); - pageSelectorController = loader.getController(); - searchParams.getPageProperty().addListener((observable, oldPage, newPage) -> loadClients()); - } catch (IOException e) { - e.printStackTrace(); - } - } - private void initializeHeader() { FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/core/main/HeaderView.fxml", + "/org/chainoptim/desktop/core/main/ListHeaderView.fxml", controllerFactory::createController ); try { Node headerView = loader.load(); headerContainer.getChildren().add(headerView); headerController = loader.getController(); - headerController.initializeHeader("Clients", "/img/truck-arrow-right-solid.png", sortOptions, "Client", "Create-Client"); + headerController.initializeHeader("Clients", "/img/truck-arrow-right-solid.png", sortOptions, this::loadClients, "Client", "Create-Client"); } catch (IOException e) { e.printStackTrace(); } @@ -125,22 +113,46 @@ private void loadFallbackManager() { fallbackContainer.getChildren().add(fallbackView); } + private void initializePageSelector() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", + controllerFactory::createController + ); + try { + Node pageSelectorView = loader.load(); + pageSelectorContainer.getChildren().add(pageSelectorView); + pageSelectorController = loader.getController(); + searchParams.getPageProperty().addListener((observable, oldPage, newPage) -> loadClients()); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void setUpListeners() { searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadClients()); searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadClients()); searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadClients()); + + // Listen to empty fallback state + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + clientsScrollPane.setVisible(newValue); + clientsScrollPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadClients() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { Platform.runLater(() -> fallbackManager.setLoading(false)); return; } - - fallbackManager.setLoading(true); - Integer organizationId = currentUser.getOrganization().getId(); + clientService.getClientsByOrganizationIdAdvanced(organizationId, searchParams) .thenApply(this::handleClientResponse) .exceptionally(this::handleClientException) @@ -202,7 +214,7 @@ private void openClientDetails(Integer clientId) { currentSelectionService.setSelectedId(clientId); currentSelectionService.setSelectedPage("Client"); - navigationService.switchView("Client?id=" + clientId); + navigationService.switchView("Client?id=" + clientId, true); } diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/CreateClientController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/CreateClientController.java index dd360898..948ffc08 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/CreateClientController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/CreateClientController.java @@ -115,7 +115,7 @@ private void handleSubmit() { Client client = clientOptional.get(); fallbackManager.setLoading(false); currentSelectionService.setSelectedId(client.getId()); - navigationService.switchView("Client?id=" + client.getId()); + navigationService.switchView("Client?id=" + client.getId(), true); }) ) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java b/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java new file mode 100644 index 00000000..5081f6c0 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java @@ -0,0 +1,176 @@ +package org.chainoptim.desktop.features.client.controller; + +import org.chainoptim.desktop.core.abstraction.ControllerFactory; +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.client.dto.UpdateClientDTO; +import org.chainoptim.desktop.features.client.model.Client; +import org.chainoptim.desktop.features.client.service.ClientService; +import org.chainoptim.desktop.features.client.service.ClientWriteService; +import org.chainoptim.desktop.shared.common.uielements.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; + +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class UpdateClientController implements Initializable { + + private final ClientService clientService; + private final ClientWriteService clientWriteService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; + private final FallbackManager fallbackManager; + + private Client client; + + private SelectOrCreateLocationController selectOrCreateLocationController; + + @FXML + private StackPane fallbackContainer; + @FXML + private StackPane selectOrCreateLocationContainer; + @FXML + private TextField nameField; + + @Inject + public UpdateClientController( + ClientService clientService, + ClientWriteService clientWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager, + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory + ) { + this.clientService = clientService; + this.clientWriteService = clientWriteService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; + this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + loadSelectOrCreateLocation(); + loadClient(currentSelectionService.getSelectedId()); + } + + private void loadFallbackManager() { + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void loadSelectOrCreateLocation() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml", + controllerFactory::createController + ); + try { + Node selectOrCreateLocationView = loader.load(); + selectOrCreateLocationController = loader.getController(); + selectOrCreateLocationContainer.getChildren().add(selectOrCreateLocationView); + selectOrCreateLocationController.initialize(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + private void loadClient(Integer clientId) { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + clientService.getClientById(clientId) + .thenApply(this::handleClientResponse) + .exceptionally(this::handleClientException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + + private Optional handleClientResponse(Optional clientOptional) { + Platform.runLater(() -> { + if (clientOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to load client."); + return; + } + client = clientOptional.get(); + + nameField.setText(client.getName()); + selectOrCreateLocationController.setSelectedLocation(client.getLocation()); + }); + + return clientOptional; + } + + private Optional handleClientException(Throwable ex) { + Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load client.")); + return Optional.empty(); + } + + @FXML + private void handleSubmit() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + UpdateClientDTO clientDTO = getUpdateClientDTO(); + System.out.println(clientDTO); + + clientWriteService.updateClient(clientDTO) + .thenAccept(clientOptional -> + Platform.runLater(() -> { + if (clientOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to create client."); + return; + } + fallbackManager.setLoading(false); + + // Manage navigation, invalidating previous client cache + Client updatedClient = clientOptional.get(); + String clientPage = "Client?id=" + updatedClient.getId(); + NavigationServiceImpl.invalidateViewCache(clientPage); + currentSelectionService.setSelectedId(updatedClient.getId()); + navigationService.switchView(clientPage, true); + }) + ) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + private UpdateClientDTO getUpdateClientDTO() { + UpdateClientDTO clientDTO = new UpdateClientDTO(); + clientDTO.setId(client.getId()); + clientDTO.setName(nameField.getText()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + clientDTO.setCreateLocation(true); + clientDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + clientDTO.setCreateLocation(false); + clientDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + + return clientDTO; + } +} + diff --git a/src/main/java/org/chainoptim/desktop/features/client/dto/UpdateClientDTO.java b/src/main/java/org/chainoptim/desktop/features/client/dto/UpdateClientDTO.java index be7a3633..c3dc26e9 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/dto/UpdateClientDTO.java +++ b/src/main/java/org/chainoptim/desktop/features/client/dto/UpdateClientDTO.java @@ -1,10 +1,14 @@ package org.chainoptim.desktop.features.client.dto; import lombok.Data; +import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; @Data public class UpdateClientDTO { private Integer id; private String name; + private Integer locationId; + private CreateLocationDTO location; + private boolean createLocation; } diff --git a/src/main/java/org/chainoptim/desktop/features/client/service/ClientWriteServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/client/service/ClientWriteServiceImpl.java index e71e0ba2..7756176b 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/service/ClientWriteServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/client/service/ClientWriteServiceImpl.java @@ -37,8 +37,8 @@ public CompletableFuture> createClient(CreateClientDTO clientDT } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) @@ -73,11 +73,11 @@ public CompletableFuture> updateClient(UpdateClientDTO clientDT } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .headers(HEADER_KEY, headerValue) .headers("Content-Type", "application/json") .build(); diff --git a/src/main/java/org/chainoptim/desktop/features/factory/controller/CreateFactoryController.java b/src/main/java/org/chainoptim/desktop/features/factory/controller/CreateFactoryController.java index 1f2b269e..e2e71383 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/controller/CreateFactoryController.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/CreateFactoryController.java @@ -115,7 +115,7 @@ private void handleSubmit() { Factory factory = factoryOptional.get(); fallbackManager.setLoading(false); currentSelectionService.setSelectedId(factory.getId()); - navigationService.switchView("Factory?id=" + factory.getId()); + navigationService.switchView("Factory?id=" + factory.getId(), true); }) ) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoriesController.java b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoriesController.java index a29c365b..d070d90e 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoriesController.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoriesController.java @@ -8,11 +8,12 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; -import org.chainoptim.desktop.core.main.controller.HeaderController; +import org.chainoptim.desktop.core.main.controller.ListHeaderController; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.core.user.model.User; @@ -40,17 +41,19 @@ public class FactoriesController implements Initializable { private final FallbackManager fallbackManager; @FXML - private HeaderController headerController; + private ListHeaderController headerController; @FXML private PageSelectorController pageSelectorController; @FXML + private ScrollPane factoriesScrollPane; + @FXML + private VBox factoriesVBox; + @FXML private StackPane pageSelectorContainer; @FXML private StackPane fallbackContainer; @FXML private StackPane headerContainer; - @FXML - private VBox factoriesVBox; private final SearchParams searchParams; private final Map sortOptions = Map.of( @@ -81,27 +84,36 @@ public FactoriesController(FactoryService factoryService, public void initialize(URL location, ResourceBundle resourceBundle) { initializeHeader(); loadFallbackManager(); + setUpListeners(); loadFactories(); initializePageSelector(); - setUpListeners(); } private void initializeHeader() { // Load view into headerContainer and initialize it with appropriate values FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/core/main/HeaderView.fxml", + "/org/chainoptim/desktop/core/main/ListHeaderView.fxml", controllerFactory::createController ); try { Node headerView = loader.load(); headerContainer.getChildren().add(headerView); headerController = loader.getController(); - headerController.initializeHeader("Factories", "/img/industry-solid.png", sortOptions, "Factory", "Create-Factory"); + headerController.initializeHeader("Factories", "/img/industry-solid.png", sortOptions, this::loadFactories, "Factory", "Create-Factory"); } catch (IOException e) { e.printStackTrace(); } } + private void loadFallbackManager() { + // Load view into fallbackContainer + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + private void initializePageSelector() { // Load view into pageSelectorContainer and initialize it with appropriate values FXMLLoader loader = fxmlLoaderService.setUpLoader( @@ -119,32 +131,32 @@ private void initializePageSelector() { } } - private void loadFallbackManager() { - // Load view into fallbackContainer - Node fallbackView = fxmlLoaderService.loadView( - "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", - controllerFactory::createController - ); - fallbackContainer.getChildren().add(fallbackView); - } - private void setUpListeners() { // Listen to changes in search params searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadFactories()); searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadFactories()); searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadFactories()); + + // Listen to empty fallback state + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + factoriesScrollPane.setVisible(newValue); + factoriesScrollPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadFactories() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { Platform.runLater(() -> fallbackManager.setLoading(false)); return; } - - fallbackManager.setLoading(true); - Integer organizationId = currentUser.getOrganization().getId(); + factoryService.getFactoriesByOrganizationIdAdvanced(organizationId, searchParams) .thenApply(this::handleFactoryResponse) .exceptionally(this::handleFactoryException) @@ -208,6 +220,6 @@ private void openFactoryDetails(Integer factoryId) { currentSelectionService.setSelectedId(factoryId); currentSelectionService.setSelectedPage("Factory"); - navigationService.switchView("Factory?id=" + factoryId); + navigationService.switchView("Factory?id=" + factoryId, true); } } diff --git a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryController.java b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryController.java index 9857530c..6152354f 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryController.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/FactoryController.java @@ -5,7 +5,9 @@ import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import org.chainoptim.desktop.MainApplication; +import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.features.factory.model.Factory; import org.chainoptim.desktop.features.factory.service.FactoryService; @@ -17,6 +19,7 @@ import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; import java.io.IOException; import java.net.URL; @@ -26,14 +29,14 @@ public class FactoryController implements Initializable { private final FactoryService factoryService; + private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; private final FallbackManager fallbackManager; private Factory factory; - @FXML - private FactoryProductionController graphController; - @FXML private StackPane fallbackContainer; @@ -55,26 +58,44 @@ public class FactoryController implements Initializable { @Inject public FactoryController(FactoryService factoryService, + NavigationService navigationService, CurrentSelectionService currentSelectionService, + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory, FallbackManager fallbackManager) { this.factoryService = factoryService; + this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; this.fallbackManager = fallbackManager; } @Override public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + setupTabListeners(); + Integer factoryId = currentSelectionService.getSelectedId(); - if (factoryId == null) { + if (factoryId != null) { + loadFactory(factoryId); + } else { System.out.println("Missing factory id."); fallbackManager.setErrorMessage("Failed to load factory."); } + } - loadFactory(factoryId); - setupTabListeners(); + private void loadFallbackManager() { + // Load view into fallbackContainer + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); } private void loadFactory(Integer factoryId) { + fallbackManager.reset(); fallbackManager.setLoading(true); factoryService.getFactoryById(factoryId) @@ -126,6 +147,13 @@ private void setupTabListeners() { loadTabContent(performanceTab, "/org/chainoptim/desktop/features/factory/FactoryPerformanceView.fxml", this.factory); } }); + + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadTabContent(Tab tab, String fxmlFilepath, Factory factory) { @@ -143,7 +171,8 @@ private void loadTabContent(Tab tab, String fxmlFilepath, Factory factory) { @FXML private void handleEditFactory() { - System.out.println("Edit Factory Working"); + currentSelectionService.setSelectedId(factory.getId()); + navigationService.switchView("Update-Factory?id=" + factory.getId(), true); } } diff --git a/src/main/java/org/chainoptim/desktop/features/factory/controller/UpdateFactoryController.java b/src/main/java/org/chainoptim/desktop/features/factory/controller/UpdateFactoryController.java new file mode 100644 index 00000000..f1a40bee --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/UpdateFactoryController.java @@ -0,0 +1,176 @@ +package org.chainoptim.desktop.features.factory.controller; + +import org.chainoptim.desktop.core.abstraction.ControllerFactory; +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.factory.dto.UpdateFactoryDTO; +import org.chainoptim.desktop.features.factory.model.Factory; +import org.chainoptim.desktop.features.factory.service.FactoryService; +import org.chainoptim.desktop.features.factory.service.FactoryWriteService; +import org.chainoptim.desktop.shared.common.uielements.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; + +import com.google.inject.Inject; +import javafx.fxml.Initializable; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class UpdateFactoryController implements Initializable { + + private final FactoryService factoryService; + private final FactoryWriteService factoryWriteService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; + private final FallbackManager fallbackManager; + + private Factory factory; + + private SelectOrCreateLocationController selectOrCreateLocationController; + + @FXML + private StackPane fallbackContainer; + @FXML + private StackPane selectOrCreateLocationContainer; + @FXML + private TextField nameField; + + @Inject + public UpdateFactoryController( + FactoryService factoryService, + FactoryWriteService factoryWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager, + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory + ) { + this.factoryService = factoryService; + this.factoryWriteService = factoryWriteService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; + this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + loadSelectOrCreateLocation(); + loadFactory(currentSelectionService.getSelectedId()); + } + + private void loadFallbackManager() { + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void loadSelectOrCreateLocation() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml", + controllerFactory::createController + ); + try { + Node selectOrCreateLocationView = loader.load(); + selectOrCreateLocationController = loader.getController(); + selectOrCreateLocationContainer.getChildren().add(selectOrCreateLocationView); + selectOrCreateLocationController.initialize(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + private void loadFactory(Integer factoryId) { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + factoryService.getFactoryById(factoryId) + .thenApply(this::handleFactoryResponse) + .exceptionally(this::handleFactoryException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + + private Optional handleFactoryResponse(Optional factoryOptional) { + Platform.runLater(() -> { + if (factoryOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to load factory."); + return; + } + factory = factoryOptional.get(); + + nameField.setText(factory.getName()); + selectOrCreateLocationController.setSelectedLocation(factory.getLocation()); + }); + + return factoryOptional; + } + + private Optional handleFactoryException(Throwable ex) { + Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load factory.")); + return Optional.empty(); + } + + @FXML + private void handleSubmit() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + UpdateFactoryDTO factoryDTO = getUpdateFactoryDTO(); + System.out.println(factoryDTO); + + factoryWriteService.updateFactory(factoryDTO) + .thenAccept(factoryOptional -> + Platform.runLater(() -> { + if (factoryOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to create factory."); + return; + } + fallbackManager.setLoading(false); + + // Manage navigation, invalidating previous factory cache + Factory updatedFactory = factoryOptional.get(); + String factoryPage = "Factory?id=" + updatedFactory.getId(); + NavigationServiceImpl.invalidateViewCache(factoryPage); + currentSelectionService.setSelectedId(updatedFactory.getId()); + navigationService.switchView(factoryPage, true); + }) + ) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + private UpdateFactoryDTO getUpdateFactoryDTO() { + UpdateFactoryDTO factoryDTO = new UpdateFactoryDTO(); + factoryDTO.setId(factory.getId()); + factoryDTO.setName(nameField.getText()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + factoryDTO.setCreateLocation(true); + factoryDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + factoryDTO.setCreateLocation(false); + factoryDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + + return factoryDTO; + } +} + diff --git a/src/main/java/org/chainoptim/desktop/features/factory/dto/UpdateFactoryDTO.java b/src/main/java/org/chainoptim/desktop/features/factory/dto/UpdateFactoryDTO.java index d596fdf1..18848f7b 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/dto/UpdateFactoryDTO.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/dto/UpdateFactoryDTO.java @@ -1,5 +1,7 @@ package org.chainoptim.desktop.features.factory.dto; +import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; + import lombok.Data; @Data @@ -7,4 +9,7 @@ public class UpdateFactoryDTO { private Integer id; private String name; + private Integer locationId; + private CreateLocationDTO location; + private boolean createLocation; } diff --git a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryWriteServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryWriteServiceImpl.java index 09d64097..ce865d70 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryWriteServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/service/FactoryWriteServiceImpl.java @@ -1,12 +1,12 @@ package org.chainoptim.desktop.features.factory.service; -import com.fasterxml.jackson.core.type.TypeReference; import org.chainoptim.desktop.core.user.util.TokenManager; import org.chainoptim.desktop.features.factory.dto.CreateFactoryDTO; import org.chainoptim.desktop.features.factory.dto.UpdateFactoryDTO; import org.chainoptim.desktop.features.factory.model.Factory; import org.chainoptim.desktop.shared.util.JsonUtil; +import com.fasterxml.jackson.core.type.TypeReference; import java.net.HttpURLConnection; import java.net.URI; import java.net.http.HttpClient; @@ -77,7 +77,7 @@ public CompletableFuture> updateFactory(UpdateFactoryDTO facto HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .headers(HEADER_KEY, headerValue) .headers("Content-Type", "application/json") .build(); diff --git a/src/main/java/org/chainoptim/desktop/features/product/controller/CreateProductController.java b/src/main/java/org/chainoptim/desktop/features/product/controller/CreateProductController.java index aca2a3ef..2400b872 100644 --- a/src/main/java/org/chainoptim/desktop/features/product/controller/CreateProductController.java +++ b/src/main/java/org/chainoptim/desktop/features/product/controller/CreateProductController.java @@ -117,7 +117,7 @@ private void handleSubmit() { Product product = productOptional.get(); fallbackManager.setLoading(false); currentSelectionService.setSelectedId(product.getId()); - navigationService.switchView("Product?id=" + product.getId()); + navigationService.switchView("Product?id=" + product.getId(), true); }) ) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/features/product/controller/ProductController.java b/src/main/java/org/chainoptim/desktop/features/product/controller/ProductController.java index a9cc8616..5ac238db 100644 --- a/src/main/java/org/chainoptim/desktop/features/product/controller/ProductController.java +++ b/src/main/java/org/chainoptim/desktop/features/product/controller/ProductController.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.features.product.controller; import org.chainoptim.desktop.MainApplication; +import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.features.product.model.Product; import org.chainoptim.desktop.features.product.service.ProductService; @@ -17,6 +18,7 @@ import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.layout.StackPane; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; import java.io.IOException; import java.net.URL; @@ -27,6 +29,8 @@ public class ProductController implements Initializable { private final ProductService productService; private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; private final FallbackManager fallbackManager; private Product product; @@ -50,26 +54,67 @@ public class ProductController implements Initializable { @Inject public ProductController(ProductService productService, - FallbackManager fallbackManager, - CurrentSelectionService currentSelectionService) { + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager) { this.productService = productService; - this.fallbackManager = fallbackManager; this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; + this.fallbackManager = fallbackManager; } @Override public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + setupListeners(); + Integer productId = currentSelectionService.getSelectedId(); - if (productId == null) { + if (productId != null) { + loadProduct(productId); + } else { System.out.println("Missing product id."); - fallbackManager.setErrorMessage("Failed to load product."); + fallbackManager.setErrorMessage("Failed to load product: missing product ID."); } + } + + private void loadFallbackManager() { + // Load view into fallbackContainer + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void setupListeners() { + overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { + loadTabContent(overviewTab, "/org/chainoptim/desktop/features/product/ProductOverviewView.fxml", this.product); + } + }); + productionTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && productionTab.getContent() == null) { + loadTabContent(productionTab, "/org/chainoptim/desktop/features/product/ProductProductionView.fxml", this.product); + } + }); + evaluationTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && evaluationTab.getContent() == null) { + loadTabContent(evaluationTab, "/org/chainoptim/desktop/features/product/ProductEvaluationView.fxml", this.product); + } + }); - loadProduct(productId); - setupTabListeners(); + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadProduct(Integer productId) { + fallbackManager.reset(); fallbackManager.setLoading(true); productService.getProductWithStages(productId) @@ -101,24 +146,6 @@ private Optional handleProductException(Throwable ex) { return Optional.empty(); } - private void setupTabListeners() { - overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { - loadTabContent(overviewTab, "/org/chainoptim/desktop/features/product/ProductOverviewView.fxml", this.product); - } - }); - productionTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && productionTab.getContent() == null) { - loadTabContent(productionTab, "/org/chainoptim/desktop/features/product/ProductProductionView.fxml", this.product); - } - }); - evaluationTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && evaluationTab.getContent() == null) { - loadTabContent(evaluationTab, "/org/chainoptim/desktop/features/product/ProductEvaluationView.fxml", this.product); - } - }); - } - private void loadTabContent(Tab tab, String fxmlFilepath, Product product) { try { FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlFilepath)); diff --git a/src/main/java/org/chainoptim/desktop/features/product/controller/ProductsController.java b/src/main/java/org/chainoptim/desktop/features/product/controller/ProductsController.java index 21cfbd71..345a0006 100644 --- a/src/main/java/org/chainoptim/desktop/features/product/controller/ProductsController.java +++ b/src/main/java/org/chainoptim/desktop/features/product/controller/ProductsController.java @@ -2,7 +2,7 @@ import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; -import org.chainoptim.desktop.core.main.controller.HeaderController; +import org.chainoptim.desktop.core.main.controller.ListHeaderController; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.core.user.model.User; @@ -28,7 +28,6 @@ import java.net.URL; import java.util.*; - public class ProductsController implements Initializable { private final ProductService productService; @@ -40,17 +39,19 @@ public class ProductsController implements Initializable { private final SearchParams searchParams; @FXML - private HeaderController headerController; + private ListHeaderController headerController; @FXML private PageSelectorController pageSelectorController; @FXML - private StackPane pageSelectorContainer; + private ScrollPane productsScrollPane; @FXML - private StackPane fallbackContainer; + private VBox productsVBox; @FXML private StackPane headerContainer; @FXML - private VBox productsVBox; + private StackPane fallbackContainer; + @FXML + private StackPane pageSelectorContainer; private long totalCount; @@ -81,22 +82,22 @@ public ProductsController(ProductService productService, public void initialize(URL location, ResourceBundle resources) { initializeHeader(); loadFallbackManager(); - loadProducts(); setUpListeners(); + loadProducts(); initializePageSelector(); } private void initializeHeader() { // Load view into headerContainer and initialize it with appropriate values FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/core/main/HeaderView.fxml", + "/org/chainoptim/desktop/core/main/ListHeaderView.fxml", controllerFactory::createController ); try { Node headerView = loader.load(); headerContainer.getChildren().add(headerView); headerController = loader.getController(); - headerController.initializeHeader("Products", "/img/box-solid.png", sortOptions, "Product", "Create-Product"); + headerController.initializeHeader("Products", "/img/box-solid.png", sortOptions, this::loadProducts, "Product", "Create-Product"); } catch (IOException e) { e.printStackTrace(); } @@ -111,29 +112,6 @@ private void loadFallbackManager() { fallbackContainer.getChildren().add(fallbackView); } - private void loadProducts() { - User currentUser = TenantContext.getCurrentUser(); - if (currentUser == null) { - Platform.runLater(() -> fallbackManager.setLoading(false)); - return; - } - - fallbackManager.setLoading(true); - - Integer organizationId = currentUser.getOrganization().getId(); - productService.getProductsByOrganizationIdAdvanced(organizationId, searchParams) - .thenApply(this::handleProductResponse) - .exceptionally(this::handleProductException) - .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); - } - - private void setUpListeners() { - // Listen to changes in search params - searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadProducts()); - searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadProducts()); - searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadProducts()); - } - private void initializePageSelector() { // Load view into pageSelectorContainer and initialize it with appropriate values FXMLLoader loader = fxmlLoaderService.setUpLoader( @@ -150,6 +128,38 @@ private void initializePageSelector() { } } + private void setUpListeners() { + // Listen to changes in search params + searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadProducts()); + searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadProducts()); + searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadProducts()); + + // Listen to empty fallback state + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + productsScrollPane.setVisible(newValue); + productsScrollPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); + } + + private void loadProducts() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + User currentUser = TenantContext.getCurrentUser(); + if (currentUser == null) { + Platform.runLater(() -> fallbackManager.setLoading(false)); + return; + } + Integer organizationId = currentUser.getOrganization().getId(); + + productService.getProductsByOrganizationIdAdvanced(organizationId, searchParams) + .thenApply(this::handleProductResponse) + .exceptionally(this::handleProductException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + private Optional> handleProductResponse(Optional> productsOptional) { Platform.runLater(() -> { if (productsOptional.isEmpty()) { @@ -199,6 +209,6 @@ private void openProductDetails(Integer productId) { // And also encode it in the viewKey for caching purposes currentSelectionService.setSelectedId(productId); currentSelectionService.setSelectedPage("Product"); - navigationService.switchView("Product?id=" + productId); + navigationService.switchView("Product?id=" + productId, true); } } \ No newline at end of file diff --git a/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/CreateComponentController.java b/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/CreateComponentController.java index d2b42bcd..2af9f206 100644 --- a/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/CreateComponentController.java +++ b/src/main/java/org/chainoptim/desktop/features/productpipeline/controller/CreateComponentController.java @@ -117,7 +117,7 @@ private void handleSubmit() { Component component = componentOptional.get(); fallbackManager.setLoading(false); currentSelectionService.setSelectedId(component.getId()); - navigationService.switchView("Component?id=" + component.getId()); + navigationService.switchView("Component?id=" + component.getId(), true); }) ) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/features/settings/controller/SettingsController.java b/src/main/java/org/chainoptim/desktop/features/settings/controller/SettingsController.java new file mode 100644 index 00000000..9a8e5257 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/settings/controller/SettingsController.java @@ -0,0 +1,4 @@ +package org.chainoptim.desktop.features.settings.controller; + +public class SettingsController { +} diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/CreateSupplierController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/CreateSupplierController.java index 3997490b..f5dcfa44 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/CreateSupplierController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/CreateSupplierController.java @@ -113,7 +113,7 @@ private void handleSubmit() { Supplier supplier = supplierOptional.get(); fallbackManager.setLoading(false); currentSelectionService.setSelectedId(supplier.getId()); - navigationService.switchView("Supplier?id=" + supplier.getId()); + navigationService.switchView("Supplier?id=" + supplier.getId(), true); }) ) .exceptionally(ex -> { 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 2db883f6..f739b7fe 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 @@ -13,6 +13,7 @@ import org.chainoptim.desktop.MainApplication; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.util.DataReceiver; @@ -27,6 +28,7 @@ public class SupplierController implements Initializable { private final SupplierService supplierService; + private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final FXMLLoaderService fxmlLoaderService; private final ControllerFactory controllerFactory; @@ -53,11 +55,13 @@ public class SupplierController implements Initializable { @Inject public SupplierController(SupplierService supplierService, + NavigationService navigationService, CurrentSelectionService currentSelectionService, FXMLLoaderService fxmlLoaderService, ControllerFactory controllerFactory, FallbackManager fallbackManager) { this.supplierService = supplierService; + this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.fxmlLoaderService = fxmlLoaderService; this.controllerFactory = controllerFactory; @@ -66,13 +70,55 @@ public SupplierController(SupplierService supplierService, @Override public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + setupListeners(); + Integer supplierId = currentSelectionService.getSelectedId(); - if (supplier == null) { + if (supplierId != null) { + loadSupplier(supplierId); + } else { System.out.println("Missing supplier id."); fallbackManager.setErrorMessage("Failed to load supplier."); } - loadSupplier(supplierId); - setupTabListeners(); + } + + private void loadFallbackManager() { + // Load view into fallbackContainer + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void setupListeners() { + overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { + loadTabContent(overviewTab, "/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml", this.supplier); + } + }); + ordersTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && ordersTab.getContent() == null) { + loadTabContent(ordersTab, "/org/chainoptim/desktop/features/supplier/SupplierOrdersView.fxml", this.supplier); + } + }); + shipmentsTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && shipmentsTab.getContent() == null) { + loadTabContent(ordersTab, "/org/chainoptim/desktop/features/supplier/SupplierShipmentsView.fxml", this.supplier); + } + }); + performanceTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && performanceTab.getContent() == null) { + loadTabContent(performanceTab, "/org/chainoptim/desktop/features/supplier/SupplierPerformanceView.fxml", this.supplier); + } + }); + + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadSupplier(Integer supplierId) { @@ -110,29 +156,6 @@ private Optional handleSupplierException(Throwable ex) { return Optional.empty(); } - private void setupTabListeners() { - overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { - loadTabContent(overviewTab, "/org/chainoptim/desktop/features/supplier/SupplierOverviewView.fxml", this.supplier); - } - }); - ordersTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && ordersTab.getContent() == null) { - loadTabContent(ordersTab, "/org/chainoptim/desktop/features/supplier/SupplierOrdersView.fxml", this.supplier); - } - }); - shipmentsTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && shipmentsTab.getContent() == null) { - loadTabContent(ordersTab, "/org/chainoptim/desktop/features/supplier/SupplierShipmentsView.fxml", this.supplier); - } - }); - performanceTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && performanceTab.getContent() == null) { - loadTabContent(performanceTab, "/org/chainoptim/desktop/features/supplier/SupplierPerformanceView.fxml", this.supplier); - } - }); - } - private void loadTabContent(Tab tab, String fxmlFilepath, Supplier supplier) { try { FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlFilepath)); @@ -147,6 +170,8 @@ private void loadTabContent(Tab tab, String fxmlFilepath, Supplier supplier) { } @FXML - private void handleEditSupplier() {System.out.println("Edit Supplier Working");} - + private void handleEditSupplier() { + currentSelectionService.setSelectedId(supplier.getId()); + navigationService.switchView("Update-Supplier?id=" + supplier.getId(), true); + } } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SuppliersController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SuppliersController.java index bf27e86d..d99259bc 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/SuppliersController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/SuppliersController.java @@ -8,11 +8,12 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; -import org.chainoptim.desktop.core.main.controller.HeaderController; +import org.chainoptim.desktop.core.main.controller.ListHeaderController; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.core.user.model.User; @@ -41,17 +42,19 @@ public class SuppliersController implements Initializable { private final SearchParams searchParams; @FXML - private HeaderController headerController; - @FXML - private StackPane headerContainer; + private ListHeaderController headerController; @FXML private PageSelectorController pageSelectorController; @FXML - private StackPane pageSelectorContainer; + private ScrollPane suppliersScrollPane; + @FXML + private VBox suppliersVBox; + @FXML + private StackPane headerContainer; @FXML private StackPane fallbackContainer; @FXML - private VBox suppliersVBox; + private StackPane pageSelectorContainer; private long totalCount; @@ -82,36 +85,21 @@ public SuppliersController(SupplierService supplierService, public void initialize(URL location, ResourceBundle resources) { initializeHeader(); loadFallbackManager(); - loadSuppliers(); setUpListeners(); + loadSuppliers(); initializePageSelector(); } - private void initializePageSelector() { - FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", - controllerFactory::createController - ); - try { - Node pageSelectorView = loader.load(); - pageSelectorContainer.getChildren().add(pageSelectorView); - pageSelectorController = loader.getController(); - searchParams.getPageProperty().addListener((observable, oldPage, newPage) -> loadSuppliers()); - } catch (IOException e) { - e.printStackTrace(); - } - } - private void initializeHeader() { FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/core/main/HeaderView.fxml", + "/org/chainoptim/desktop/core/main/ListHeaderView.fxml", controllerFactory::createController ); try { Node headerView = loader.load(); headerContainer.getChildren().add(headerView); headerController = loader.getController(); - headerController.initializeHeader("Suppliers", "/img/truck-arrow-right-solid.png", sortOptions, "Supplier", "Create-Supplier"); + headerController.initializeHeader("Suppliers", "/img/truck-arrow-right-solid.png", sortOptions, this::loadSuppliers, "Supplier", "Create-Supplier"); } catch (IOException e) { e.printStackTrace(); } @@ -125,22 +113,46 @@ private void loadFallbackManager() { fallbackContainer.getChildren().add(fallbackView); } + private void initializePageSelector() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", + controllerFactory::createController + ); + try { + Node pageSelectorView = loader.load(); + pageSelectorContainer.getChildren().add(pageSelectorView); + pageSelectorController = loader.getController(); + searchParams.getPageProperty().addListener((observable, oldPage, newPage) -> loadSuppliers()); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void setUpListeners() { searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadSuppliers()); searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadSuppliers()); searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadSuppliers()); + + // Listen to empty fallback state + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + suppliersScrollPane.setVisible(newValue); + suppliersScrollPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadSuppliers() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { Platform.runLater(() -> fallbackManager.setLoading(false)); return; } - - fallbackManager.setLoading(true); - Integer organizationId = currentUser.getOrganization().getId(); + supplierService.getSuppliersByOrganizationIdAdvanced(organizationId, searchParams) .thenApply(this::handleSupplierResponse) .exceptionally(this::handleSupplierException) @@ -201,7 +213,7 @@ private void openSupplierDetails(Integer supplierId) { currentSelectionService.setSelectedId(supplierId); currentSelectionService.setSelectedPage("Supplier"); - navigationService.switchView("Supplier?id=" + supplierId); + navigationService.switchView("Supplier?id=" + supplierId, true); } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/controller/UpdateSupplierController.java b/src/main/java/org/chainoptim/desktop/features/supplier/controller/UpdateSupplierController.java new file mode 100644 index 00000000..a9be0e0a --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/UpdateSupplierController.java @@ -0,0 +1,175 @@ +package org.chainoptim.desktop.features.supplier.controller; + +import org.chainoptim.desktop.core.abstraction.ControllerFactory; +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.supplier.dto.UpdateSupplierDTO; +import org.chainoptim.desktop.features.supplier.model.Supplier; +import org.chainoptim.desktop.features.supplier.service.SupplierService; +import org.chainoptim.desktop.features.supplier.service.SupplierWriteService; +import org.chainoptim.desktop.shared.common.uielements.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; + +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class UpdateSupplierController implements Initializable { + + private final SupplierService supplierService; + private final SupplierWriteService supplierWriteService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; + private final FallbackManager fallbackManager; + + private Supplier supplier; + + private SelectOrCreateLocationController selectOrCreateLocationController; + + @FXML + private StackPane fallbackContainer; + @FXML + private StackPane selectOrCreateLocationContainer; + @FXML + private TextField nameField; + + @Inject + public UpdateSupplierController( + SupplierService supplierService, + SupplierWriteService supplierWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager, + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory + ) { + this.supplierService = supplierService; + this.supplierWriteService = supplierWriteService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; + this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + loadSelectOrCreateLocation(); + loadSupplier(currentSelectionService.getSelectedId()); + } + + private void loadFallbackManager() { + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void loadSelectOrCreateLocation() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml", + controllerFactory::createController + ); + try { + Node selectOrCreateLocationView = loader.load(); + selectOrCreateLocationController = loader.getController(); + selectOrCreateLocationContainer.getChildren().add(selectOrCreateLocationView); + selectOrCreateLocationController.initialize(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + private void loadSupplier(Integer supplierId) { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + supplierService.getSupplierById(supplierId) + .thenApply(this::handleSupplierResponse) + .exceptionally(this::handleSupplierException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + + private Optional handleSupplierResponse(Optional supplierOptional) { + Platform.runLater(() -> { + if (supplierOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to load supplier."); + return; + } + supplier = supplierOptional.get(); + + nameField.setText(supplier.getName()); + selectOrCreateLocationController.setSelectedLocation(supplier.getLocation()); + }); + + return supplierOptional; + } + + private Optional handleSupplierException(Throwable ex) { + Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load supplier.")); + return Optional.empty(); + } + + @FXML + private void handleSubmit() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + UpdateSupplierDTO supplierDTO = getUpdateSupplierDTO(); + System.out.println(supplierDTO); + + supplierWriteService.updateSupplier(supplierDTO) + .thenAccept(supplierOptional -> + Platform.runLater(() -> { + if (supplierOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to create supplier."); + return; + } + fallbackManager.setLoading(false); + + // Manage navigation, invalidating previous supplier cache + Supplier updatedSupplier = supplierOptional.get(); + String supplierPage = "Supplier?id=" + updatedSupplier.getId(); + NavigationServiceImpl.invalidateViewCache(supplierPage); + currentSelectionService.setSelectedId(updatedSupplier.getId()); + navigationService.switchView(supplierPage, true); + }) + ) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + private UpdateSupplierDTO getUpdateSupplierDTO() { + UpdateSupplierDTO supplierDTO = new UpdateSupplierDTO(); + supplierDTO.setId(supplier.getId()); + supplierDTO.setName(nameField.getText()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + supplierDTO.setCreateLocation(true); + supplierDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + supplierDTO.setCreateLocation(false); + supplierDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + + return supplierDTO; + } +} + diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/dto/UpdateSupplierDTO.java b/src/main/java/org/chainoptim/desktop/features/supplier/dto/UpdateSupplierDTO.java index 94a05ebc..25f9460e 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/dto/UpdateSupplierDTO.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/dto/UpdateSupplierDTO.java @@ -1,10 +1,14 @@ package org.chainoptim.desktop.features.supplier.dto; import lombok.Data; +import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; @Data public class UpdateSupplierDTO { private Integer id; private String name; + private Integer locationId; + private CreateLocationDTO location; + private boolean createLocation; } diff --git a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierWriteServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierWriteServiceImpl.java index d7e03727..2617f6f6 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierWriteServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/service/SupplierWriteServiceImpl.java @@ -37,8 +37,8 @@ public CompletableFuture> createSupplier(CreateSupplierDTO su } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) @@ -73,12 +73,13 @@ public CompletableFuture> updateSupplier(UpdateSupplierDTO su } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .headers(HEADER_KEY, headerValue) + .headers("Content-Type", "application/json") .build(); return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/CreateWarehouseController.java b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/CreateWarehouseController.java index ef1ab1de..28883646 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/CreateWarehouseController.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/CreateWarehouseController.java @@ -115,7 +115,7 @@ private void handleSubmit() { fallbackManager.setLoading(false); currentSelectionService.setSelectedId(warehouse.getId()); System.out.println(warehouseDTO); - navigationService.switchView("Warehouse?id=" + warehouse.getId()); + navigationService.switchView("Warehouse?id=" + warehouse.getId(), true); }) ) .exceptionally(ex -> { diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/UpdateWarehouseController.java b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/UpdateWarehouseController.java new file mode 100644 index 00000000..c754deb0 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/UpdateWarehouseController.java @@ -0,0 +1,176 @@ +package org.chainoptim.desktop.features.warehouse.controller; + +import org.chainoptim.desktop.core.abstraction.ControllerFactory; +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.dto.UpdateWarehouseDTO; +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.common.uielements.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; + +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class UpdateWarehouseController implements Initializable { + + private final WarehouseService warehouseService; + private final WarehouseWriteService warehouseWriteService; + private final NavigationService navigationService; + private final CurrentSelectionService currentSelectionService; + private final FXMLLoaderService fxmlLoaderService; + private final ControllerFactory controllerFactory; + private final FallbackManager fallbackManager; + + private Warehouse warehouse; + + private SelectOrCreateLocationController selectOrCreateLocationController; + + @FXML + private StackPane fallbackContainer; + @FXML + private StackPane selectOrCreateLocationContainer; + @FXML + private TextField nameField; + + @Inject + public UpdateWarehouseController( + WarehouseService warehouseService, + WarehouseWriteService warehouseWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + FallbackManager fallbackManager, + FXMLLoaderService fxmlLoaderService, + ControllerFactory controllerFactory + ) { + this.warehouseService = warehouseService; + this.warehouseWriteService = warehouseWriteService; + this.navigationService = navigationService; + this.currentSelectionService = currentSelectionService; + this.fxmlLoaderService = fxmlLoaderService; + this.controllerFactory = controllerFactory; + this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + loadSelectOrCreateLocation(); + loadWarehouse(currentSelectionService.getSelectedId()); + } + + private void loadFallbackManager() { + Node fallbackView = fxmlLoaderService.loadView( + "/org/chainoptim/desktop/shared/fallback/FallbackManagerView.fxml", + controllerFactory::createController + ); + fallbackContainer.getChildren().add(fallbackView); + } + + private void loadSelectOrCreateLocation() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml", + controllerFactory::createController + ); + try { + Node selectOrCreateLocationView = loader.load(); + selectOrCreateLocationController = loader.getController(); + selectOrCreateLocationContainer.getChildren().add(selectOrCreateLocationView); + selectOrCreateLocationController.initialize(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + private void loadWarehouse(Integer warehouseId) { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + warehouseService.getWarehouseById(warehouseId) + .thenApply(this::handleWarehouseResponse) + .exceptionally(this::handleWarehouseException) + .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + } + + private Optional handleWarehouseResponse(Optional warehouseOptional) { + Platform.runLater(() -> { + if (warehouseOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to load warehouse."); + return; + } + warehouse = warehouseOptional.get(); + + nameField.setText(warehouse.getName()); + selectOrCreateLocationController.setSelectedLocation(warehouse.getLocation()); + }); + + return warehouseOptional; + } + + private Optional handleWarehouseException(Throwable ex) { + Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load warehouse.")); + return Optional.empty(); + } + + @FXML + private void handleSubmit() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + + UpdateWarehouseDTO warehouseDTO = getUpdateWarehouseDTO(); + System.out.println(warehouseDTO); + + warehouseWriteService.updateWarehouse(warehouseDTO) + .thenAccept(warehouseOptional -> + Platform.runLater(() -> { + if (warehouseOptional.isEmpty()) { + fallbackManager.setErrorMessage("Failed to create warehouse."); + return; + } + fallbackManager.setLoading(false); + + // Manage navigation, invalidating previous warehouse cache + Warehouse updatedWarehouse = warehouseOptional.get(); + String warehousePage = "Warehouse?id=" + updatedWarehouse.getId(); + NavigationServiceImpl.invalidateViewCache(warehousePage); + currentSelectionService.setSelectedId(updatedWarehouse.getId()); + navigationService.switchView(warehousePage, true); + }) + ) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + private UpdateWarehouseDTO getUpdateWarehouseDTO() { + UpdateWarehouseDTO warehouseDTO = new UpdateWarehouseDTO(); + warehouseDTO.setId(warehouse.getId()); + warehouseDTO.setName(nameField.getText()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + warehouseDTO.setCreateLocation(true); + warehouseDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + warehouseDTO.setCreateLocation(false); + warehouseDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + + return warehouseDTO; + } +} + 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 1fdb81c3..72d6631c 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 @@ -13,6 +13,7 @@ import org.chainoptim.desktop.MainApplication; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; +import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.features.supplier.service.SupplierService; import org.chainoptim.desktop.features.warehouse.model.Warehouse; @@ -28,6 +29,7 @@ public class WarehouseController implements Initializable { private final WarehouseService warehouseService; + private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final FXMLLoaderService fxmlLoaderService; private final ControllerFactory controllerFactory; @@ -50,11 +52,13 @@ public class WarehouseController implements Initializable { @Inject public WarehouseController(WarehouseService warehouseService, + NavigationService navigationService, CurrentSelectionService currentSelectionService, FXMLLoaderService fxmlLoaderService, ControllerFactory controllerFactory, FallbackManager fallbackManager) { this.warehouseService = warehouseService; + this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.fxmlLoaderService = fxmlLoaderService; this.controllerFactory = controllerFactory; @@ -62,15 +66,17 @@ public WarehouseController(WarehouseService warehouseService, } @Override - public void initialize (URL location, ResourceBundle resources) { + public void initialize(URL location, ResourceBundle resources) { + loadFallbackManager(); + setupListeners(); + Integer warehouseId = currentSelectionService.getSelectedId(); - if (warehouseId == null) { + if (warehouseId != null) { + loadWarehouse(warehouseId); + } else { System.out.println("Missing warehouse id."); fallbackManager.setErrorMessage("Failed to load warehouse."); } - loadFallbackManager(); - loadWarehouse(warehouseId); - setupTabListeners(); } private void loadFallbackManager() { @@ -82,6 +88,26 @@ private void loadFallbackManager() { fallbackContainer.getChildren().add(fallbackView); } + private void setupListeners() { + overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { + loadTabContent(overviewTab, "/org/chainoptim/desktop/features/warehouse/WarehouseOverviewView.fxml", this.warehouse); + } + }); + inventoryTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { + if (Boolean.TRUE.equals(isNowSelected) && inventoryTab.getContent() == null) { + loadTabContent(inventoryTab, "/org/chainoptim/desktop/features/warehouse/WarehouseInventoryView.fxml", this.warehouse); + } + }); + + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + tabPane.setVisible(newValue); + tabPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); + } + private void loadWarehouse(Integer warehouseId) { fallbackManager.reset(); fallbackManager.setLoading(true); @@ -113,19 +139,6 @@ private Optional handleWarehouseException(Throwable ex) { return Optional.empty(); } - private void setupTabListeners() { - overviewTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && overviewTab.getContent() == null) { - loadTabContent(overviewTab, "/org/chainoptim/desktop/features/warehouse/WarehouseOverviewView.fxml", this.warehouse); - } - }); - inventoryTab.selectedProperty().addListener((observable, wasSelected, isNowSelected) -> { - if (Boolean.TRUE.equals(isNowSelected) && inventoryTab.getContent() == null) { - loadTabContent(inventoryTab, "/org/chainoptim/desktop/features/warehouse/WarehouseInventoryView.fxml", this.warehouse); - } - }); - } - private void loadTabContent(Tab tab, String fxmlFilepath, Warehouse warehouse) { try { FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlFilepath)); @@ -141,11 +154,7 @@ private void loadTabContent(Tab tab, String fxmlFilepath, Warehouse warehouse) { @FXML private void handleEditWarehouse() { - System.out.println("Edit Warehouse Working"); + currentSelectionService.setSelectedId(warehouse.getId()); + navigationService.switchView("Update-Warehouse?id=" + warehouse.getId(), true); } - - - - - } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehousesController.java b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehousesController.java index 5973ab91..7de47fbb 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehousesController.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/WarehousesController.java @@ -8,15 +8,15 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; -import org.chainoptim.desktop.core.main.controller.HeaderController; +import org.chainoptim.desktop.core.main.controller.ListHeaderController; import org.chainoptim.desktop.core.main.service.CurrentSelectionService; import org.chainoptim.desktop.core.main.service.NavigationServiceImpl; import org.chainoptim.desktop.core.user.model.User; -import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.features.warehouse.model.Warehouse; import org.chainoptim.desktop.features.warehouse.service.WarehouseService; import org.chainoptim.desktop.shared.fallback.FallbackManager; @@ -42,17 +42,19 @@ public class WarehousesController implements Initializable { private final SearchParams searchParams; @FXML - private HeaderController headerController; - @FXML - private StackPane headerContainer; + private ListHeaderController headerController; @FXML private PageSelectorController pageSelectorController; @FXML + private ScrollPane warehousesScrollPane; + @FXML + private VBox warehousesVBox; + @FXML + private StackPane headerContainer; + @FXML private StackPane pageSelectorContainer; @FXML private StackPane fallbackContainer; - @FXML - private VBox warehousesVBox; private long totalCount; @@ -84,36 +86,21 @@ public WarehousesController(WarehouseService warehouseService, public void initialize(URL location, ResourceBundle resources) { initializeHeader(); loadFallbackManager(); - loadWarehouses(); setUpListeners(); + loadWarehouses(); initializePageSelector(); } - private void initializePageSelector() { - FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", - controllerFactory::createController - ); - try { - Node pageSelectorView = loader.load(); - pageSelectorContainer.getChildren().add(pageSelectorView); - pageSelectorController = loader.getController(); - searchParams.getPageProperty().addListener((observable, oldValue, newValue) -> loadWarehouses()); - } catch (IOException e) { - e.printStackTrace(); - } - } - private void initializeHeader() { FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/core/main/HeaderView.fxml", + "/org/chainoptim/desktop/core/main/ListHeaderView.fxml", controllerFactory::createController ); try { Node headerView = loader.load(); headerContainer.getChildren().add(headerView); headerController = loader.getController(); - headerController.initializeHeader("Warehouses", "/img/warehouse-solid.png", sortOptions, "Warehouse", "Create-Warehouse"); + headerController.initializeHeader("Warehouses", "/img/warehouse-solid.png", sortOptions, this::loadWarehouses, "Warehouse", "Create-Warehouse"); } catch (IOException e) { e.printStackTrace(); } @@ -127,21 +114,46 @@ private void loadFallbackManager() { fallbackContainer.getChildren().add(fallbackView); } + private void initializePageSelector() { + FXMLLoader loader = fxmlLoaderService.setUpLoader( + "/org/chainoptim/desktop/shared/search/PageSelectorView.fxml", + controllerFactory::createController + ); + try { + Node pageSelectorView = loader.load(); + pageSelectorContainer.getChildren().add(pageSelectorView); + pageSelectorController = loader.getController(); + searchParams.getPageProperty().addListener((observable, oldValue, newValue) -> loadWarehouses()); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void setUpListeners() { searchParams.getSearchQueryProperty().addListener((observable, oldValue, newValue) -> loadWarehouses()); searchParams.getAscendingProperty().addListener((observable, oldValue, newValue) -> loadWarehouses()); searchParams.getSortOptionProperty().addListener((observable, oldValue, newValue) -> loadWarehouses()); + + // Listen to empty fallback state + fallbackManager.isEmptyProperty().addListener((observable, oldValue, newValue) -> { + warehousesScrollPane.setVisible(newValue); + warehousesScrollPane.setManaged(newValue); + fallbackContainer.setVisible(!newValue); + fallbackContainer.setManaged(!newValue); + }); } private void loadWarehouses() { + fallbackManager.reset(); + fallbackManager.setLoading(true); + User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { Platform.runLater(() -> fallbackManager.setLoading(false)); return; } - fallbackManager.setLoading(true); - Integer organizationId = currentUser.getOrganization().getId(); + warehouseService.getWarehousesByOrganizationIdAdvanced(organizationId, searchParams) .thenApply(this::handleWarehouseResponse) .exceptionally(this::handleWarehouseException) @@ -203,7 +215,7 @@ private void openWarehouseDetails(Integer warehouseId) { currentSelectionService.setSelectedId(warehouseId); currentSelectionService.setSelectedPage("Warehouse"); - navigationService.switchView("Warehouse?id=" + warehouseId); + navigationService.switchView("Warehouse?id=" + warehouseId, true); } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/dto/UpdateWarehouseDTO.java b/src/main/java/org/chainoptim/desktop/features/warehouse/dto/UpdateWarehouseDTO.java index c2a89a36..fd330c73 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/dto/UpdateWarehouseDTO.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/dto/UpdateWarehouseDTO.java @@ -1,10 +1,14 @@ package org.chainoptim.desktop.features.warehouse.dto; import lombok.Data; +import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; @Data public class UpdateWarehouseDTO { private Integer id; private String name; + private Integer locationId; + private CreateLocationDTO location; + private boolean createLocation; } diff --git a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseWriteServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseWriteServiceImpl.java index 52382159..5226d63e 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseWriteServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/service/WarehouseWriteServiceImpl.java @@ -37,8 +37,8 @@ public CompletableFuture> createWarehouse(CreateWarehouseDTO } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) @@ -73,12 +73,13 @@ public CompletableFuture> updateWarehouse(UpdateWarehouseDTO } catch (Exception e) { e.printStackTrace(); } - assert requestBody != null; + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .headers(HEADER_KEY, headerValue) + .headers("Content-Type", "application/json") .build(); return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationController.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationController.java index 7a50a9cb..d85e5f5a 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationController.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationController.java @@ -123,6 +123,11 @@ public Location getSelectedLocation() { return locationComboBox.getSelectionModel().getSelectedItem(); } + public void setSelectedLocation(Location location) { + locationComboBox.getSelectionModel().select(location); + + } + public CreateLocationDTO getNewLocationDTO() { CreateLocationDTO locationDTO = new CreateLocationDTO(); locationDTO.setOrganizationId(organizationId); diff --git a/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManager.java b/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManager.java index 7862d52c..f65124b5 100644 --- a/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManager.java +++ b/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManager.java @@ -12,6 +12,11 @@ public class FallbackManager { private final BooleanProperty isLoading = new SimpleBooleanProperty(false); private final BooleanProperty noOrganization = new SimpleBooleanProperty(false); private final BooleanProperty noResults = new SimpleBooleanProperty(false); + private final BooleanProperty isEmpty = new SimpleBooleanProperty(true); + + public FallbackManager() { + isEmpty.bind(isLoadingProperty().not().and(noOrganizationProperty().not().and(noResultsProperty().not().and(errorMessageProperty().isEmpty())))); + } public String getErrorMessage() { return errorMessage.get(); @@ -67,4 +72,12 @@ public void reset() { setNoOrganization(false); setNoResults(false); } + + public BooleanProperty isEmptyProperty() { + return isEmpty; + } + + public boolean isEmpty() { + return isEmpty.get(); + } } \ No newline at end of file diff --git a/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManagerController.java b/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManagerController.java index cc006514..037b85aa 100644 --- a/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManagerController.java +++ b/src/main/java/org/chainoptim/desktop/shared/fallback/FallbackManagerController.java @@ -4,13 +4,12 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import lombok.Data; import java.io.IOException; -import java.net.URL; -import java.util.Objects; +import java.util.HashMap; +import java.util.Map; @Data public class FallbackManagerController { @@ -20,52 +19,68 @@ public class FallbackManagerController { private FallbackManager fallbackManager; + private Map loadedViews = new HashMap<>(); + private Map loadedControllers = new HashMap<>(); + @Inject public FallbackManagerController(FallbackManager fallbackManager) { this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize() { + loadFallbackViews(); setupChangeListeners(); + updateView(); + } + + private void loadFallbackViews() { + String[] viewPaths = { + "/org/chainoptim/desktop/shared/fallback/ErrorFallbackView.fxml", + "/org/chainoptim/desktop/shared/fallback/LoadingFallbackView.fxml", + "/org/chainoptim/desktop/shared/fallback/NoOrganizationFallbackView.fxml", + "/org/chainoptim/desktop/shared/fallback/NoResultsFallbackView.fxml" + }; + + for (String path : viewPaths) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource(path)); + Node view = loader.load(); + Object controller = loader.getController(); + + loadedViews.put(path, view); + loadedControllers.put(path, controller); + } catch (IOException e) { + e.printStackTrace(); + } + } } private void setupChangeListeners() { fallbackManager.errorMessageProperty().addListener((obs, oldVal, newVal) -> updateView()); - fallbackManager.isLoadingProperty().addListener((obs, oldVal, newVal) -> { - System.out.println("isLoading changed to: " + newVal); - updateView(); - }); + fallbackManager.isLoadingProperty().addListener((obs, oldVal, newVal) -> updateView()); fallbackManager.noOrganizationProperty().addListener((obs, oldVal, newVal) -> updateView()); fallbackManager.noResultsProperty().addListener((obs, oldVal, newVal) -> updateView()); } - @FXML - public void initialize() { - updateView(); - } - private void updateView() { String viewPath = determineViewPathBasedOnState(); + if (!viewPath.isEmpty()) { - try { - URL url = getClass().getResource(viewPath); - if (url == null) { - return; - } - System.out.println("Update to view: " + url); - FXMLLoader loader = new FXMLLoader(url); - Node fallbackView = loader.load(); - if (viewPath == "/org/chainoptim/desktop/shared/fallback/ErrorFallbackView.fxml") { - ErrorFallbackController controller = loader.getController(); - controller.initialize(fallbackManager.getErrorMessage()); - } - fallbackContentHolder.getChildren().setAll(fallbackView); - } catch (IOException e) { - e.printStackTrace(); + Node view = loadedViews.get(viewPath); + // Set error message in case of error + Object controller = loadedControllers.get(viewPath); + if (viewPath.equals("/org/chainoptim/desktop/shared/fallback/ErrorFallbackView.fxml")) { + ((ErrorFallbackController)controller).initialize(fallbackManager.getErrorMessage()); } + + fallbackContentHolder.getChildren().setAll(view); } else { - // No fallback fallbackContentHolder.getChildren().clear(); fallbackContentHolder.setPrefSize(0, 0); } } + private String determineViewPathBasedOnState() { if (!fallbackManager.getErrorMessage().isEmpty()) { return "/org/chainoptim/desktop/shared/fallback/ErrorFallbackView.fxml"; diff --git a/src/main/java/org/chainoptim/desktop/shared/fallback/LoadingFallbackController.java b/src/main/java/org/chainoptim/desktop/shared/fallback/LoadingFallbackController.java index 2f5df69d..adce5cba 100644 --- a/src/main/java/org/chainoptim/desktop/shared/fallback/LoadingFallbackController.java +++ b/src/main/java/org/chainoptim/desktop/shared/fallback/LoadingFallbackController.java @@ -1,28 +1,35 @@ package org.chainoptim.desktop.shared.fallback; -import javafx.animation.FadeTransition; +import javafx.animation.Animation; +import javafx.animation.Interpolator; +import javafx.animation.RotateTransition; import javafx.fxml.FXML; -import javafx.scene.Group; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressIndicator; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.util.Duration; +import java.util.Objects; + public class LoadingFallbackController { @FXML - private ProgressIndicator loadingSpinner; + private ImageView customSpinner; @FXML public void initialize() { - loadingSpinner.setVisible(true); + Image image = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/spinner-solid.png"))); + customSpinner.setImage(image); + customSpinner.setFitHeight(52); + customSpinner.setFitWidth(52); + applySpinAnimation(customSpinner); } + public void applySpinAnimation(Node node) { + RotateTransition rotateTransition = new RotateTransition(Duration.seconds(2), node); + rotateTransition.setByAngle(360); + rotateTransition.setCycleCount(Animation.INDEFINITE); + rotateTransition.setInterpolator(Interpolator.LINEAR); + rotateTransition.play(); + } } diff --git a/src/main/java/org/chainoptim/desktop/shared/features/location/model/Location.java b/src/main/java/org/chainoptim/desktop/shared/features/location/model/Location.java index 1fc78b21..8e0e4928 100644 --- a/src/main/java/org/chainoptim/desktop/shared/features/location/model/Location.java +++ b/src/main/java/org/chainoptim/desktop/shared/features/location/model/Location.java @@ -23,4 +23,9 @@ public String getFormattedLocation() { return formattedCity + formattedState + formattedCountry; } + @Override + public String toString() { + return getFormattedLocation(); + } + } diff --git a/src/main/java/org/chainoptim/desktop/shared/search/controller/PageSelectorController.java b/src/main/java/org/chainoptim/desktop/shared/search/controller/PageSelectorController.java index 094ae1ae..1eb1726e 100644 --- a/src/main/java/org/chainoptim/desktop/shared/search/controller/PageSelectorController.java +++ b/src/main/java/org/chainoptim/desktop/shared/search/controller/PageSelectorController.java @@ -14,33 +14,24 @@ public class PageSelectorController { - @FXML private Pagination pagination; private final SearchParams searchParams; -// private final PaginatedResults paginatedResults; -private long totalItems; @Inject - public PageSelectorController( - SearchParams searchParams - // PaginatedResults paginatedResults - ) { + public PageSelectorController(SearchParams searchParams) { this.searchParams = searchParams; - //this.paginatedResults = paginatedResults; } @FXML public void initialize(long totalItems) { int itemsPerPage = searchParams.getItemsPerPage(); - this.totalItems = totalItems; int pageCount = (int) Math.ceil((double) totalItems / itemsPerPage); pagination.setPageCount(pageCount); pagination.setCurrentPageIndex(searchParams.getPage() - 1); - pagination.currentPageIndexProperty().addListener((obs, oldIndex, newIndex) -> { - searchParams.setPage(newIndex.intValue() + 1); - }); + pagination.currentPageIndexProperty().addListener((obs, oldIndex, newIndex) -> + searchParams.setPage(newIndex.intValue() + 1) + ); } - } diff --git a/src/main/java/org/chainoptim/desktop/shared/search/model/SearchParams.java b/src/main/java/org/chainoptim/desktop/shared/search/model/SearchParams.java index b66adcf0..c5848aa2 100644 --- a/src/main/java/org/chainoptim/desktop/shared/search/model/SearchParams.java +++ b/src/main/java/org/chainoptim/desktop/shared/search/model/SearchParams.java @@ -7,18 +7,18 @@ public class SearchParams { - private StringProperty searchQuery; - private StringProperty sortOption; - private BooleanProperty ascending; - private IntegerProperty page; - private IntegerProperty itemsPerPage; + private final StringProperty searchQuery; + private final StringProperty sortOption; + private final BooleanProperty ascending; + private final IntegerProperty page; + private final IntegerProperty itemsPerPage; public SearchParams() { searchQuery = new SimpleStringProperty(""); sortOption = new SimpleStringProperty("createdAt"); ascending = new SimpleBooleanProperty(true); page = new SimpleIntegerProperty(1); - itemsPerPage = new SimpleIntegerProperty(4); + itemsPerPage = new SimpleIntegerProperty(10); } public BooleanProperty getAscendingProperty() { diff --git a/src/main/resources/css/buttons.css b/src/main/resources/css/buttons.css index c5a77a54..84a48037 100644 --- a/src/main/resources/css/buttons.css +++ b/src/main/resources/css/buttons.css @@ -1,14 +1,16 @@ .standard-write-button { - -fx-background-color: #0077dd; + -fx-pref-height: 32px; + -fx-background-color: #006AEE; -fx-text-fill: white; -fx-font-weight: normal; - -fx-font-size: 15px; + -fx-font-size: 14px; -fx-padding: 4px 12px 4px 12px; + -fx-graphic-text-gap: 8px; -fx-cursor: hand; } .standard-write-button:hover { - -fx-background-color: #0066bb; + -fx-background-color: #0068C9; } /* Button with default JavaFX styles removed */ diff --git a/src/main/resources/css/entity-card.css b/src/main/resources/css/entity-card.css index 0a2ea356..02d45246 100644 --- a/src/main/resources/css/entity-card.css +++ b/src/main/resources/css/entity-card.css @@ -1,14 +1,14 @@ .entity-card { - -fx-background-color: #f4f3f7; - -fx-border-color: #a9adad; + -fx-background-color: #f1f2f2; + -fx-border-color: #bdbfad; -fx-border-width: 1px; -fx-border-radius: 5px; - -fx-padding: 8px; + -fx-padding: 8px 12px; } .entity-card:hover { -fx-cursor: hand; - -fx-background-color: #e3e2e6; + -fx-background-color: #e8e8e8; } .entity-name-label { diff --git a/src/main/resources/css/fallback.css b/src/main/resources/css/fallback.css new file mode 100644 index 00000000..abd376f2 --- /dev/null +++ b/src/main/resources/css/fallback.css @@ -0,0 +1,36 @@ +.fallback-container { + -fx-spacing: 10px; +} + +.fallback-title { + -fx-font-size: 20px; + -fx-font-weight: bold; +} + +.fallback-text { + -fx-font-size: 16px; + -fx-font-weight: normal; +} + +.error-message { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #111111; +} + +.spinner-rotate { + -fx-animation: spin 2s infinite linear; +} + +@keyframes spin { + from { + -fx-rotate: 0; + } + to { + -fx-rotate: 360; + } +} + +.page-fallback-container { + -fx-padding: 160px; +} \ No newline at end of file diff --git a/src/main/resources/css/forms.css b/src/main/resources/css/forms.css index 2e1a24a5..53ba412c 100644 --- a/src/main/resources/css/forms.css +++ b/src/main/resources/css/forms.css @@ -14,6 +14,7 @@ -fx-margin-bottom: 4px; -fx-font-size: 18px; -fx-font-weight: bold; + -fx-padding: 4px; } .form-title { diff --git a/src/main/resources/css/globals.css b/src/main/resources/css/globals.css index 282eed8a..75551d75 100644 --- a/src/main/resources/css/globals.css +++ b/src/main/resources/css/globals.css @@ -6,4 +6,6 @@ @import url("entity-card.css"); @import url("entity-page.css"); @import url("table.css"); -@import url("factory-production.css"); \ No newline at end of file +@import url("factory-production.css"); +@import url("fallback.css"); +@import url("organization.css"); \ No newline at end of file diff --git a/src/main/resources/css/list-header.css b/src/main/resources/css/list-header.css index 2eb0b34d..bc489486 100644 --- a/src/main/resources/css/list-header.css +++ b/src/main/resources/css/list-header.css @@ -1,20 +1,23 @@ .header { -fx-background-color: #ffffff; - -fx-padding: 20px; -fx-spacing: 20px; } .toolbar { -fx-background-color: #ffffff; + -fx-padding: 0px 32px 32px 32px; } .list-title { - -fx-font-size: 30px; + -fx-font-size: 26px; -fx-font-weight: bold; + -fx-padding: 20px 32px 0px 32px; -fx-spacing: 20px; + -fx-graphic-text-gap: 12px; } .search-bar { + -fx-pref-height: 32px; -fx-background-color: #ffffff; -fx-border-color: #c0c0c0; -fx-border-width: 1px; @@ -22,16 +25,62 @@ } .search-button { + -fx-pref-height: 32px; + -fx-pref-width: 32px; + -fx-background-color: #232323; + -fx-border-radius: 4px; +} + +.search-button:hover { + -fx-cursor: hand; + -fx-background-color: #000000; +} + +.header .combo-box { + -fx-pref-height: 32px; -fx-background-color: #ffffff; + -fx-font-weight: bold; -fx-border-color: #c0c0c0; -fx-border-width: 1px; -fx-border-radius: 4px; + -fx-cursor: hand; +} +.header .combo-box .arrow-button { + -fx-background-color: transparent; + -fx-background-insets: 0; } -.search-button:hover { +.header .combo-box .arrow { + -fx-background-color: #c0c0c0; +} + +.ordering-button { + -fx-pref-height: 32px; + -fx-background-color: transparent; + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; + -fx-border-color: #c0c0c0; + -fx-border-width: 1px; + -fx-border-radius: 4px; -fx-cursor: hand; +} + +.ordering-button:hover { -fx-background-color: #f0f0f0; } +.refresh-button { + -fx-pref-height: 32px; + -fx-background-color: transparent; + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; + -fx-border-color: #c0c0c0; + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-cursor: hand; +} +.refresh-button:hover { + -fx-background-color: #f0f0f0; +} \ No newline at end of file diff --git a/src/main/resources/css/organization.css b/src/main/resources/css/organization.css new file mode 100644 index 00000000..080348ab --- /dev/null +++ b/src/main/resources/css/organization.css @@ -0,0 +1,16 @@ +.plan-label { + -fx-alignment: top-right; + -fx-text-alignment: right; + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.organization-content-container { + -fx-padding: 20px; + +} + +.organization-common-label { + -fx-font-size: 14px; + -fx-font-weight: bold; +} \ No newline at end of file diff --git a/src/main/resources/css/page-elements.css b/src/main/resources/css/page-elements.css index d2bfd8d1..59da7e88 100644 --- a/src/main/resources/css/page-elements.css +++ b/src/main/resources/css/page-elements.css @@ -5,7 +5,7 @@ .items-list { -fx-background-color: #ffffff; - -fx-padding: 20px; + -fx-padding: 32px; -fx-spacing: 20px; } @@ -43,22 +43,6 @@ -fx-text-fill: white; } - -/* Everything related to the fallbacks: */ -.fallback-container { - -fx-spacing: 10px; -} - -.fallback-title { - -fx-font-size: 20px; - -fx-font-weight: bold; -} - -.fallback-text { - -fx-font-size: 16px; - -fx-font-weight: normal; -} - /* Just for tests: */ .test { -fx-background-color: #efb3b3; diff --git a/src/main/resources/css/sidebar.css b/src/main/resources/css/sidebar.css index 09e9527d..80a6c093 100644 --- a/src/main/resources/css/sidebar.css +++ b/src/main/resources/css/sidebar.css @@ -1,4 +1,3 @@ - .sidebar { -fx-background-color: #000000; -fx-border-color: #333; @@ -16,7 +15,6 @@ -fx-alignment: center-left; } - .sidebar-button { -fx-background-color: #000000; -fx-text-fill: white; @@ -27,6 +25,7 @@ -fx-min-height: 40; -fx-max-height: 48; -fx-alignment: baseline-left; + -fx-graphic-text-gap: 16px; -fx-cursor: hand; } diff --git a/src/main/resources/img/arrow-left-solid.png b/src/main/resources/img/arrow-left-solid.png new file mode 100644 index 00000000..63259da2 Binary files /dev/null and b/src/main/resources/img/arrow-left-solid.png differ diff --git a/src/main/resources/img/building-solid.png b/src/main/resources/img/building-solid.png new file mode 100644 index 00000000..5ab61b31 Binary files /dev/null and b/src/main/resources/img/building-solid.png differ diff --git a/src/main/resources/img/gear-solid.png b/src/main/resources/img/gear-solid.png new file mode 100644 index 00000000..f9ade6f1 Binary files /dev/null and b/src/main/resources/img/gear-solid.png differ diff --git a/src/main/resources/img/magnifying-glass-solid.png b/src/main/resources/img/magnifying-glass-solid.png index ac176852..566bf2aa 100644 Binary files a/src/main/resources/img/magnifying-glass-solid.png and b/src/main/resources/img/magnifying-glass-solid.png differ diff --git a/src/main/resources/img/rotate-right-solid.png b/src/main/resources/img/rotate-right-solid.png new file mode 100644 index 00000000..873db7e5 Binary files /dev/null and b/src/main/resources/img/rotate-right-solid.png differ diff --git a/src/main/resources/img/sort-up.png b/src/main/resources/img/sort-up.png index 16952060..428c355d 100644 Binary files a/src/main/resources/img/sort-up.png and b/src/main/resources/img/sort-up.png differ diff --git a/src/main/resources/img/spinner-solid.png b/src/main/resources/img/spinner-solid.png new file mode 100644 index 00000000..51ae9841 Binary files /dev/null and b/src/main/resources/img/spinner-solid.png differ diff --git a/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml b/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml index 62aca284..bf159ffa 100644 --- a/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml @@ -1,15 +1,10 @@ - - - - - - + fx:controller="org.chainoptim.desktop.core.main.controller.AppController" + style="-fx-background-color: #ffffff;"> @@ -18,8 +13,7 @@
- +
-
\ No newline at end of file diff --git a/src/main/resources/org/chainoptim/desktop/core/main/HeaderView.fxml b/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml similarity index 81% rename from src/main/resources/org/chainoptim/desktop/core/main/HeaderView.fxml rename to src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml index 7e58119f..7ad6296f 100644 --- a/src/main/resources/org/chainoptim/desktop/core/main/HeaderView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml @@ -4,7 +4,7 @@ diff --git a/src/main/resources/org/chainoptim/desktop/core/organization/OrganizationView.fxml b/src/main/resources/org/chainoptim/desktop/core/organization/OrganizationView.fxml index 31271241..c5fb0947 100644 --- a/src/main/resources/org/chainoptim/desktop/core/organization/OrganizationView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/organization/OrganizationView.fxml @@ -2,10 +2,28 @@ - + fx:controller="org.chainoptim.desktop.core.organization.controller.OrganizationController"> - - + + + + + + +