From 2bf8952bb3d2de005023b4c35ea28100386fad59 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:29:30 +0300 Subject: [PATCH 01/10] Finish refactoring services --- .../core/main/controller/AppController.java | 19 +- .../NotificationPersistenceService.java | 5 +- .../NotificationPersistenceServiceImpl.java | 86 +++---- .../controller/OrganizationController.java | 27 ++- .../OrganizationCustomRolesController.java | 21 +- .../OrganizationOverviewController.java | 41 ++-- .../service/CustomRoleService.java | 9 +- .../service/CustomRoleServiceImpl.java | 152 ++++--------- .../service/OrganizationService.java | 3 +- .../service/OrganizationServiceImpl.java | 56 ++--- .../controller/OverviewController.java | 25 +-- .../service/SupplyChainSnapshotService.java | 3 +- .../SupplyChainSnapshotServiceImpl.java | 55 ++--- .../controller/SettingsController.java | 25 ++- .../settings/service/UserSettingsService.java | 5 +- .../service/UserSettingsServiceImpl.java | 91 +++----- ...blicUsersSearchAndSelectionController.java | 11 +- .../UsersListByCustomRoleController.java | 13 +- .../core/user/service/UserService.java | 13 +- .../core/user/service/UserServiceImpl.java | 210 ++++-------------- .../SelectOrCreateLocationController.java | 13 +- .../location/service/LocationService.java | 9 +- .../location/service/LocationServiceImpl.java | 156 ++++--------- 23 files changed, 343 insertions(+), 705 deletions(-) diff --git a/src/main/java/org/chainoptim/desktop/core/main/controller/AppController.java b/src/main/java/org/chainoptim/desktop/core/main/controller/AppController.java index 7da2cd46..01a641c5 100644 --- a/src/main/java/org/chainoptim/desktop/core/main/controller/AppController.java +++ b/src/main/java/org/chainoptim/desktop/core/main/controller/AppController.java @@ -18,6 +18,7 @@ import org.chainoptim.desktop.core.user.service.AuthenticationService; import org.chainoptim.desktop.core.user.service.UserService; import org.chainoptim.desktop.core.user.util.TokenManager; +import org.chainoptim.desktop.shared.httphandling.Result; import java.net.URI; import java.net.URISyntaxException; @@ -77,36 +78,34 @@ public void initialize() { private void fetchAndSetUser(String username) { userService.getUserByUsername(username) .thenApply(this::handleUserResponse) - .exceptionally(ex -> Optional.empty()); + .exceptionally(ex -> new Result<>()); } - private Optional<User> handleUserResponse(Optional<User> userOptional) { + private Result<User> handleUserResponse(Result<User> result) { Platform.runLater(() -> { - System.out.println("User loaded: " + userOptional); - if (userOptional.isEmpty()) { + if (result.getError() != null) { return; } - User user = userOptional.get(); + User user = result.getData(); user.getOrganization().setSubscriptionPlanTier(PRO); - System.out.println("Subscription plan: " + user.getOrganization().getSubscriptionPlanTier().toString()); // Set user to TenantContext for reuse throughout the app TenantContext.setCurrentUser(user); // Fetch user settings and set them to TenantSettingsContext userSettingsService.getUserSettings(user.getId()) - .thenAccept(userSettingsOptional -> { - if (userSettingsOptional.isEmpty()) return; - TenantSettingsContext.setCurrentUserSettings(userSettingsOptional.get()); + .thenAccept(result1 -> { + if (result1.getError() != null) return; + TenantSettingsContext.setCurrentUserSettings(result1.getData()); }); // Start WebSocket connection startWebSocket(user); }); - return userOptional; + return result; } private void startWebSocket(User user) { diff --git a/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceService.java b/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceService.java index 255ace67..46020193 100644 --- a/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceService.java +++ b/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceService.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.core.notification.service; import org.chainoptim.desktop.core.notification.model.NotificationUser; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import org.chainoptim.desktop.shared.search.model.SearchParams; @@ -10,7 +11,7 @@ public interface NotificationPersistenceService { - CompletableFuture<Optional<List<NotificationUser>>> getNotificationsByUserId(String userId); + CompletableFuture<Result<List<NotificationUser>>> getNotificationsByUserId(String userId); - CompletableFuture<Optional<PaginatedResults<NotificationUser>>> getNotificationsByUserIdAdvanced(String userId, SearchParams searchParams); + CompletableFuture<Result<PaginatedResults<NotificationUser>>> getNotificationsByUserIdAdvanced(String userId, SearchParams searchParams); } diff --git a/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceServiceImpl.java index 1af477ea..1dd44b9e 100644 --- a/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/notification/service/NotificationPersistenceServiceImpl.java @@ -1,100 +1,66 @@ package org.chainoptim.desktop.core.notification.service; -import org.chainoptim.desktop.core.notification.model.Notification; import org.chainoptim.desktop.core.notification.model.NotificationUser; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.features.factory.dto.FactoriesSearchDTO; -import org.chainoptim.desktop.features.supplier.model.Supplier; +import org.chainoptim.desktop.core.user.service.TokenManager; import org.chainoptim.desktop.shared.caching.CacheKeyBuilder; import org.chainoptim.desktop.shared.caching.CachingService; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import org.chainoptim.desktop.shared.search.model.SearchParams; -import org.chainoptim.desktop.shared.util.JsonUtil; + import com.fasterxml.jackson.core.type.TypeReference; import com.google.inject.Inject; 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.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; public class NotificationPersistenceServiceImpl implements NotificationPersistenceService { private final CachingService<PaginatedResults<NotificationUser>> cachingService; - private final HttpClient client = HttpClient.newHttpClient(); + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; private static final int STALE_TIME = 300; @Inject - public NotificationPersistenceServiceImpl(CachingService<PaginatedResults<NotificationUser>> cachingService) { + public NotificationPersistenceServiceImpl( + CachingService<PaginatedResults<NotificationUser>> cachingService, + RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { this.cachingService = cachingService; + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; } - public CompletableFuture<Optional<List<NotificationUser>>> getNotificationsByUserId(String userId) { + public CompletableFuture<Result<List<NotificationUser>>> getNotificationsByUserId(String userId) { String routeAddress = "http://localhost:8080/api/v1/notifications/user/" + userId; - 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(); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.<List<NotificationUser>>empty(); - try { - List<NotificationUser> notifications = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<List<NotificationUser>>() {}); - return Optional.of(notifications); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<List<NotificationUser>>empty(); - } - }); + return requestHandler.sendRequest(request, new TypeReference<List<NotificationUser>>() {}); } - public CompletableFuture<Optional<PaginatedResults<NotificationUser>>> getNotificationsByUserIdAdvanced(String userId, SearchParams searchParams) { + public CompletableFuture<Result<PaginatedResults<NotificationUser>>> getNotificationsByUserIdAdvanced(String userId, SearchParams searchParams) { String rootAddress = "http://localhost:8080/api/v1/"; String cacheKey = CacheKeyBuilder.buildAdvancedSearchKey("notifications", "user", userId, searchParams); String routeAddress = rootAddress + cacheKey; - 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(); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); if (cachingService.isCached(cacheKey) && !cachingService.isStale(cacheKey)) { - return CompletableFuture.completedFuture(Optional.of(cachingService.get(cacheKey))); + return CompletableFuture.completedFuture(new Result<>(cachingService.get(cacheKey), null, HttpURLConnection.HTTP_OK)); } - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.<PaginatedResults<NotificationUser>>empty(); - try { - PaginatedResults<NotificationUser> notifications = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<PaginatedResults<NotificationUser>>() {}); - - cachingService.remove(cacheKey); // Ensure there isn't a stale cache entry - cachingService.add(cacheKey, notifications, STALE_TIME); - - return Optional.of(notifications); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<PaginatedResults<NotificationUser>>empty(); - } - }); + return requestHandler.sendRequest(request, new TypeReference<PaginatedResults<NotificationUser>>() {}, notifications -> { + cachingService.remove(cacheKey); // Ensure there isn't a stale cache entry + cachingService.add(cacheKey, notifications, STALE_TIME); + }); } } 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 1b728639..3b86f7d3 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 @@ -9,6 +9,7 @@ import org.chainoptim.desktop.core.organization.service.OrganizationService; import org.chainoptim.desktop.core.user.model.User; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; @@ -160,18 +161,16 @@ private void loadOrganization() { .exceptionally(this::handleOrganizationException); } - private Optional<Organization> handleOrganizationResponse(Optional<Organization> organizationOptional) { + private Result<Organization> handleOrganizationResponse(Result<Organization> result) { Platform.runLater(() -> { - if (organizationOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to load organization."); return; } - organizationViewData.setOrganization(organizationOptional.get()); + organizationViewData.setOrganization(result.getData()); organizationViewData.getOrganization().setSubscriptionPlanTier(PRO); - System.out.println("Organization: " + organizationViewData.getOrganization()); - initializeUI(); organizationOverviewController.setData(organizationViewData); // Feed organization data to overview tab @@ -181,7 +180,7 @@ private Optional<Organization> handleOrganizationResponse(Optional<Organization> fallbackManager.setLoading(false); }); - return organizationOptional; + return result; } private void loadCustomRoles() { @@ -191,24 +190,24 @@ private void loadCustomRoles() { .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); } - private Optional<List<CustomRole>> handleCustomRolesResponse(Optional<List<CustomRole>> customRolesOptional) { + private Result<List<CustomRole>> handleCustomRolesResponse(Result<List<CustomRole>> result) { Platform.runLater(() -> { - if (customRolesOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to load custom roles."); return; } - organizationViewData.setCustomRoles(customRolesOptional.get()); + organizationViewData.setCustomRoles(result.getData()); if (organizationOverviewController != null) { organizationOverviewController.setData(organizationViewData); } System.out.println("Custom Roles: " + organizationViewData.getCustomRoles()); }); - return customRolesOptional; + return result; } - private Optional<List<CustomRole>> handleCustomRolesException(Throwable ex) { + private Result<List<CustomRole>> handleCustomRolesException(Throwable ex) { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load custom roles.")); - return Optional.empty(); + return new Result<>(); } private void initializeUI() { @@ -217,9 +216,9 @@ private void initializeUI() { planLabel.setText("Subscription Plan: " + organizationViewData.getOrganization().getSubscriptionPlanTier().toString()); } - private Optional<Organization> handleOrganizationException(Throwable ex) { + private Result<Organization> handleOrganizationException(Throwable ex) { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load organization.")); - return Optional.empty(); + return new Result<>(); } @FXML diff --git a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationCustomRolesController.java b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationCustomRolesController.java index e3bc5dde..470da321 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationCustomRolesController.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationCustomRolesController.java @@ -9,6 +9,7 @@ import org.chainoptim.desktop.shared.confirmdialog.controller.RunnableConfirmDialogActionListener; import org.chainoptim.desktop.shared.confirmdialog.model.ConfirmDialogInput; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; @@ -505,13 +506,13 @@ private UpdateCustomRoleDTO gatherUpdatedCustomRole(CustomRole customRole, int s return new UpdateCustomRoleDTO(customRole.getId(), roleName, permissions); } - private void handleSuccessfulUpdate(Optional<CustomRole> updatedRoleOptional, int rowIndex) { + private void handleSuccessfulUpdate(Result<CustomRole> result, int rowIndex) { Platform.runLater(() -> { - if (updatedRoleOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to update custom role."); return; } - CustomRole updatedRole = updatedRoleOptional.get(); + CustomRole updatedRole = result.getData(); closeConfirmUpdateDialog(); // Update customRoles @@ -570,9 +571,9 @@ private void handleDeleteRole(CustomRole customRole) { .thenAccept(deletedRoleIdOptional -> handleSuccessfulDelete(deletedRoleIdOptional, currentToBeDeletedRowIndex)); } - private void handleSuccessfulDelete(Optional<Integer> deletedRoleIdOptional, int rowIndex) { + private void handleSuccessfulDelete(Result<Integer> result, int rowIndex) { Platform.runLater(() -> { - if (deletedRoleIdOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to delete custom role."); return; } @@ -603,17 +604,17 @@ private void handleAddNewRole() { .thenApply(this::handleNewRoleResponse) .exceptionally(ex -> { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to create new custom role.")); - return Optional.empty(); + return new Result<>(); }); } - private Optional<CustomRole> handleNewRoleResponse(Optional<CustomRole> newRoleOptional) { + private Result<CustomRole> handleNewRoleResponse(Result<CustomRole> result) { Platform.runLater(() -> { - if (newRoleOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to create new custom role."); return; } - CustomRole newRole = newRoleOptional.get(); + CustomRole newRole = result.getData(); customRoles.add(newRole); tabTitle.setText("Custom Roles (" + customRoles.size() + ")"); @@ -625,7 +626,7 @@ private Optional<CustomRole> handleNewRoleResponse(Optional<CustomRole> newRoleO addFeaturePermissionRow(feature, newRole, newRowIndex++); } }); - return newRoleOptional; + return result; } // Utils diff --git a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationOverviewController.java b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationOverviewController.java index a0b5581f..de219293 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationOverviewController.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/controller/OrganizationOverviewController.java @@ -11,6 +11,7 @@ import org.chainoptim.desktop.shared.confirmdialog.controller.RunnableConfirmDialogActionListener; import org.chainoptim.desktop.shared.confirmdialog.model.ConfirmDialogInput; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; @@ -290,16 +291,16 @@ private void assignBasicRole(Pair<String, User.Role> userIdRole) { .exceptionally(this::handleBasicRoleException); } - private Optional<User> handleBasicRoleResponse(Optional<User> userOptional) { + private Result<User> handleBasicRoleResponse(Result<User> result) { Platform.runLater(() -> { - if (userOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to assign basic role."); return; } fallbackManager.setLoading(false); // Update Users list with updated user - User updatedUser = userOptional.get(); + User updatedUser = result.getData(); organizationViewData.getOrganization().getUsers().stream() .filter(user -> user.getId().equals(updatedUser.getId())) .findFirst().ifPresent(user -> user.setRole(updatedUser.getRole())); @@ -311,19 +312,19 @@ private Optional<User> handleBasicRoleResponse(Optional<User> userOptional) { if (assignNode == null) return; membersGridPane.getChildren().remove(assignNode); - Button button = addBasicRoleButton(userOptional.get(), editedUserRowIndex); + Button button = addBasicRoleButton(updatedUser, editedUserRowIndex); applyStandardMargin(button); membersGridPane.add(button, ASSIGN_BASIC_ROLE_COLUMN_INDEX, editedUserRowIndex); GridPane.setHalignment(button, HPos.CENTER); editedUserRowIndex = -1; }); - return userOptional; + return result; } - private Optional<User> handleBasicRoleException(Throwable throwable) { + private Result<User> handleBasicRoleException(Throwable throwable) { fallbackManager.setErrorMessage("Failed to assign basic role."); - return Optional.empty(); + return new Result<>(); } private void cancelAssignBasicRole() { @@ -364,16 +365,16 @@ private void assignCustomRole(Pair<String, Integer> userIdCustomRoleId) { .exceptionally(this::handleCustomRoleException); } - private Optional<User> handleCustomRoleResponse(Optional<User> userOptional) { + private Result<User> handleCustomRoleResponse(Result<User> result) { Platform.runLater(() -> { - if (userOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to assign custom role."); return; } fallbackManager.setLoading(false); // Update users list with updated user - User updatedUser = userOptional.get(); + User updatedUser = result.getData(); organizationViewData.getOrganization().getUsers().stream() .filter(user -> user.getId().equals(updatedUser.getId())) .findFirst().ifPresent(user -> user.setCustomRole(updatedUser.getCustomRole())); @@ -385,19 +386,19 @@ private Optional<User> handleCustomRoleResponse(Optional<User> userOptional) { if (assignNode == null) return; membersGridPane.getChildren().remove(assignNode); - Button button = addCustomRoleButton(userOptional.get(), editedUserRowIndex); + Button button = addCustomRoleButton(updatedUser, editedUserRowIndex); applyStandardMargin(button); membersGridPane.add(button, ASSIGN_ROLE_COLUMN_INDEX, editedUserRowIndex); GridPane.setHalignment(button, HPos.CENTER); editedUserRowIndex = -1; }); - return userOptional; + return result; } - private Optional<User> handleCustomRoleException(Throwable throwable) { + private Result<User> handleCustomRoleException(Throwable throwable) { fallbackManager.setErrorMessage("Failed to assign custom role."); - return Optional.empty(); + return new Result<>(); } private void cancelAssignCustomRole() { @@ -442,13 +443,13 @@ private void removeOrganizationMember(Pair<String, Integer> userIdOrganizationId .exceptionally(this::handleRemoveMemberException); } - private Optional<User> handleRemoveMemberResponse(Optional<User> userOptional) { + private Result<User> handleRemoveMemberResponse(Result<User> result) { Platform.runLater(() -> { - if (userOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to remove member."); return; } - User user = userOptional.get(); + User user = result.getData(); toggleRemoveDialog(false); fallbackManager.setLoading(false); @@ -460,12 +461,12 @@ private Optional<User> handleRemoveMemberResponse(Optional<User> userOptional) { // Re-render members grid - necessary here, at least from that user down renderMembersGrid(); }); - return userOptional; + return result; } - private Optional<User> handleRemoveMemberException(Throwable throwable) { + private Result<User> handleRemoveMemberException(Throwable throwable) { fallbackManager.setErrorMessage("Failed to remove member."); - return Optional.empty(); + return new Result<>(); } private void cancelRemoveMember() { diff --git a/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleService.java b/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleService.java index 5dfda068..9b28c319 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleService.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleService.java @@ -3,6 +3,7 @@ import org.chainoptim.desktop.core.organization.dto.CreateCustomRoleDTO; import org.chainoptim.desktop.core.organization.dto.UpdateCustomRoleDTO; import org.chainoptim.desktop.core.organization.model.CustomRole; +import org.chainoptim.desktop.shared.httphandling.Result; import java.util.List; import java.util.Optional; @@ -10,8 +11,8 @@ public interface CustomRoleService { - CompletableFuture<Optional<List<CustomRole>>> getCustomRolesByOrganizationId(Integer organizationId); - CompletableFuture<Optional<CustomRole>> createCustomRole(CreateCustomRoleDTO roleDTO); - CompletableFuture<Optional<CustomRole>> updateCustomRole(UpdateCustomRoleDTO roleDTO); - CompletableFuture<Optional<Integer>> deleteCustomRole(Integer roleId); + CompletableFuture<Result<List<CustomRole>>> getCustomRolesByOrganizationId(Integer organizationId); + CompletableFuture<Result<CustomRole>> createCustomRole(CreateCustomRoleDTO roleDTO); + CompletableFuture<Result<CustomRole>> updateCustomRole(UpdateCustomRoleDTO roleDTO); + CompletableFuture<Result<Integer>> deleteCustomRole(Integer roleId); } diff --git a/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleServiceImpl.java index bc726e50..f3d17574 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/CustomRoleServiceImpl.java @@ -3,142 +3,66 @@ import org.chainoptim.desktop.core.organization.dto.CreateCustomRoleDTO; import org.chainoptim.desktop.core.organization.dto.UpdateCustomRoleDTO; import org.chainoptim.desktop.core.organization.model.CustomRole; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.shared.util.JsonUtil; +import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.shared.httphandling.HttpMethod; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; import com.fasterxml.jackson.core.type.TypeReference; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.http.HttpClient; +import com.google.inject.Inject; + import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; public class CustomRoleServiceImpl implements CustomRoleService { - private final HttpClient client = HttpClient.newHttpClient(); - - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; + + @Inject + public CustomRoleServiceImpl(RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } - public CompletableFuture<Optional<List<CustomRole>>> getCustomRolesByOrganizationId(Integer organizationId) { + public CompletableFuture<Result<List<CustomRole>>> getCustomRolesByOrganizationId(Integer organizationId) { String routeAddress = "http://localhost:8080/api/v1/custom-roles/organization/" + organizationId.toString(); - 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.<List<CustomRole>>empty(); - try { - List<CustomRole> customRoles = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<List<CustomRole>>() {}); - return Optional.of(customRoles); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<List<CustomRole>>empty(); - } - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<List<CustomRole>>() {}); } - public CompletableFuture<Optional<CustomRole>> createCustomRole(CreateCustomRoleDTO roleDTO) { + public CompletableFuture<Result<CustomRole>> createCustomRole(CreateCustomRoleDTO roleDTO) { String routeAddress = "http://localhost:8080/api/v1/custom-roles/create"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(roleDTO); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) - .headers(HEADER_KEY, headerValue) - .headers("Content-Type", "application/json") - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - CustomRole customRole = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<CustomRole>() {}); - return Optional.of(customRole); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<CustomRole>empty(); - } - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.POST, routeAddress, tokenManager.getToken(), roleDTO); + + return requestHandler.sendRequest(request, new TypeReference<CustomRole>() {}); } - public CompletableFuture<Optional<CustomRole>> updateCustomRole(UpdateCustomRoleDTO roleDTO) { + public CompletableFuture<Result<CustomRole>> updateCustomRole(UpdateCustomRoleDTO roleDTO) { String routeAddress = "http://localhost:8080/api/v1/custom-roles/update"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(roleDTO); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .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()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - CustomRole customRole = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<CustomRole>() {}); - return Optional.of(customRole); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<CustomRole>empty(); - } - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), roleDTO); + + return requestHandler.sendRequest(request, new TypeReference<CustomRole>() {}); } - public CompletableFuture<Optional<Integer>> deleteCustomRole(Integer roleId) { + public CompletableFuture<Result<Integer>> deleteCustomRole(Integer roleId) { String routeAddress = "http://localhost:8080/api/v1/custom-roles/delete/" + roleId; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .DELETE() - .headers(HEADER_KEY, headerValue) - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - return Optional.of(roleId); - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.DELETE, routeAddress, tokenManager.getToken(), null); + + return requestHandler.sendRequest(request, new TypeReference<Integer>() {}); } } 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 40408cd9..59cb929a 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 @@ -2,12 +2,13 @@ import org.chainoptim.desktop.core.organization.model.Organization; import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.shared.httphandling.Result; import java.util.Optional; import java.util.concurrent.CompletableFuture; public interface OrganizationService { - CompletableFuture<Optional<Organization>> getOrganizationById(Integer organizationId, boolean includeUsers); + CompletableFuture<Result<Organization>> 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 index 9a2c49d2..4ede8f4b 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java @@ -1,49 +1,37 @@ package org.chainoptim.desktop.core.organization.service; import org.chainoptim.desktop.core.organization.model.Organization; -import org.chainoptim.desktop.core.user.model.User; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.shared.util.JsonUtil; +import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; import com.fasterxml.jackson.core.type.TypeReference; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.http.HttpClient; +import com.google.inject.Inject; + 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 "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; + + @Inject + public OrganizationServiceImpl(RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } - public CompletableFuture<Optional<Organization>> getOrganizationById(Integer organizationId, boolean includeUsers) { + public CompletableFuture<Result<Organization>> 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.<Organization>empty(); - try { - Organization organization = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<Organization>() {}); - return Optional.of(organization); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<Organization>empty(); - } - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<Organization>() {}); } } diff --git a/src/main/java/org/chainoptim/desktop/core/overview/controller/OverviewController.java b/src/main/java/org/chainoptim/desktop/core/overview/controller/OverviewController.java index 8a0cbed3..718d9513 100644 --- a/src/main/java/org/chainoptim/desktop/core/overview/controller/OverviewController.java +++ b/src/main/java/org/chainoptim/desktop/core/overview/controller/OverviewController.java @@ -14,6 +14,7 @@ import org.chainoptim.desktop.shared.common.uielements.badge.BadgeData; import org.chainoptim.desktop.shared.common.uielements.badge.FeatureCountBadge; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import org.chainoptim.desktop.shared.search.model.SearchParams; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; @@ -127,8 +128,6 @@ private void initializeUI() { headerImageView.setFitHeight(16); headerImageView.setFitWidth(16); titleLabel.setGraphic(headerImageView); - -// alertImage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/alert-circle-solid.png"))); } private void loadData(User currentUser) { @@ -144,46 +143,46 @@ private void loadData(User currentUser) { .thenApply(this::handleSupplyChainSnapshotResponse) .exceptionally(ex -> { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load supply chain snapshot.")); - return Optional.empty(); + return new Result<>(); }); notificationPersistenceService.getNotificationsByUserIdAdvanced(currentUser.getId(), searchParams) .thenApply(this::handleNotificationsResponse) .exceptionally(ex -> { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load notifications.")); - return Optional.empty(); + return new Result<>(); }); } // Fetches - private Optional<SupplyChainSnapshot> handleSupplyChainSnapshotResponse(Optional<SupplyChainSnapshot> snapshotOptional) { + private Result<SupplyChainSnapshot> handleSupplyChainSnapshotResponse(Result<SupplyChainSnapshot> result) { Platform.runLater(() -> { - if (snapshotOptional.isEmpty()) { + if (result.getError() != null) { return; } - snapshot = snapshotOptional.get().getSnapshot(); + snapshot = result.getData().getSnapshot(); snapshotContext.setSnapshot(snapshot); fallbackManager.setLoading(false); renderEntityCountsVBox(snapshot); }); - return snapshotOptional; + return result; } - private Optional<PaginatedResults<NotificationUser>> handleNotificationsResponse(Optional<PaginatedResults<NotificationUser>> notificationsOptional) { + private Result<PaginatedResults<NotificationUser>> handleNotificationsResponse(Result<PaginatedResults<NotificationUser>> result) { Platform.runLater(() -> { - if (notificationsOptional.isEmpty()) { + if (result.getError() != null) { return; } - PaginatedResults<NotificationUser> notifications = notificationsOptional.get(); + PaginatedResults<NotificationUser> notifications = result.getData(); fallbackManager.setLoading(false); totalCount = notifications.getTotalCount(); renderNotificationsVBox(notifications.getResults()); }); - return notificationsOptional; + return result; } // UI Rendering @@ -271,7 +270,7 @@ private void addLoadMoreButton() { .thenApply(this::handleNotificationsResponse) .exceptionally(ex -> { Platform.runLater(() -> fallbackManager.setErrorMessage("Failed to load more notifications.")); - return Optional.empty(); + return new Result<>(); }); }); notificationsVBox.getChildren().add(currentLoadMoreButton); diff --git a/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotService.java b/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotService.java index 8d334c8e..8c9e7e46 100644 --- a/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotService.java +++ b/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotService.java @@ -1,11 +1,12 @@ package org.chainoptim.desktop.core.overview.service; import org.chainoptim.desktop.core.overview.model.SupplyChainSnapshot; +import org.chainoptim.desktop.shared.httphandling.Result; import java.util.Optional; import java.util.concurrent.CompletableFuture; public interface SupplyChainSnapshotService { - CompletableFuture<Optional<SupplyChainSnapshot>> getSupplyChainSnapshot(Integer organizationId); + CompletableFuture<Result<SupplyChainSnapshot>> getSupplyChainSnapshot(Integer organizationId); } diff --git a/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotServiceImpl.java index d65724e2..e8ed6626 100644 --- a/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/overview/service/SupplyChainSnapshotServiceImpl.java @@ -1,49 +1,38 @@ package org.chainoptim.desktop.core.overview.service; import org.chainoptim.desktop.core.overview.model.SupplyChainSnapshot; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.shared.util.JsonUtil; +import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.inject.Inject; -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 SupplyChainSnapshotServiceImpl implements SupplyChainSnapshotService { - private final HttpClient client = HttpClient.newHttpClient(); - - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final org.chainoptim.desktop.core.user.service.TokenManager tokenManager; + + @Inject + public SupplyChainSnapshotServiceImpl( + RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } - public CompletableFuture<Optional<SupplyChainSnapshot>> getSupplyChainSnapshot(Integer organizationId) { + public CompletableFuture<Result<SupplyChainSnapshot>> getSupplyChainSnapshot(Integer organizationId) { String routeAddress = "http://localhost:8080/api/v1/supply-chain-snapshots/organization/" + organizationId.toString(); - 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.<SupplyChainSnapshot>empty(); - try { - SupplyChainSnapshot snapshot = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<SupplyChainSnapshot>() {}); - return Optional.of(snapshot); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<SupplyChainSnapshot>empty(); - } - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<SupplyChainSnapshot>() {}); } } diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java index 42042d05..98ea8089 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java @@ -8,6 +8,7 @@ import org.chainoptim.desktop.core.settings.service.UserSettingsService; import org.chainoptim.desktop.core.user.model.User; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; @@ -155,25 +156,25 @@ private void loadUserSettings(String userId) { .exceptionally(this::handleUserSettingsException); } - private Optional<UserSettings> handleUserSettingsResponse(Optional<UserSettings> userSettingsOptional) { + private Result<UserSettings> handleUserSettingsResponse(Result<UserSettings> result) { Platform.runLater(() -> { - if (userSettingsOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to load user settings."); return; } - userSettings = userSettingsOptional.get(); + userSettings = result.getData(); fallbackManager.setLoading(false); loadTabContent(generalTab, "/org/chainoptim/desktop/core/settings/GeneralSettingsView.fxml", userSettings); }); - return userSettingsOptional; + return result; } - private Optional<UserSettings> handleUserSettingsException(Throwable ex) { + private Result<UserSettings> handleUserSettingsException(Throwable ex) { fallbackManager.setErrorMessage("Failed to load user settings."); - return Optional.empty(); + return new Result<>(); } @Override @@ -197,13 +198,13 @@ public void handleSave() { .exceptionally(this::handleSaveException); } - private Optional<UserSettings> handleSaveResponse(Optional<UserSettings> userSettingsOptional) { + private Result<UserSettings> handleSaveResponse(Result<UserSettings> result) { Platform.runLater(() -> { - if (userSettingsOptional.isEmpty()) { + if (result.getError() != null) { fallbackManager.setErrorMessage("Failed to save user settings."); return; } - userSettings = userSettingsOptional.get(); + userSettings = result.getData(); TenantSettingsContext.setCurrentUserSettings(userSettings.deepCopy()); if (generalSettingsController != null) { @@ -217,12 +218,12 @@ private Optional<UserSettings> handleSaveResponse(Optional<UserSettings> userSet handleSettingsChanged(false); }); - return userSettingsOptional; + return result; } - private Optional<UserSettings> handleSaveException(Throwable ex) { + private Result<UserSettings> handleSaveException(Throwable ex) { fallbackManager.setErrorMessage("Failed to save user settings."); - return Optional.empty(); + return new Result<>(); } @FXML diff --git a/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsService.java b/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsService.java index a8443e93..3f4a7cdc 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsService.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsService.java @@ -2,13 +2,14 @@ import org.chainoptim.desktop.core.settings.dto.UpdateUserSettingsDTO; import org.chainoptim.desktop.core.settings.model.UserSettings; +import org.chainoptim.desktop.shared.httphandling.Result; import java.util.Optional; import java.util.concurrent.CompletableFuture; public interface UserSettingsService { - CompletableFuture<Optional<UserSettings>> getUserSettings(String userId); + CompletableFuture<Result<UserSettings>> getUserSettings(String userId); - CompletableFuture<Optional<UserSettings>> saveUserSettings(UpdateUserSettingsDTO userSettings); + CompletableFuture<Result<UserSettings>> saveUserSettings(UpdateUserSettingsDTO userSettings); } diff --git a/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsServiceImpl.java index d729525d..ac35c00d 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/service/UserSettingsServiceImpl.java @@ -2,84 +2,47 @@ import org.chainoptim.desktop.core.settings.dto.UpdateUserSettingsDTO; import org.chainoptim.desktop.core.settings.model.UserSettings; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.shared.util.JsonUtil; +import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.shared.httphandling.HttpMethod; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.inject.Inject; -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.nio.charset.StandardCharsets; -import java.util.Optional; import java.util.concurrent.CompletableFuture; public class UserSettingsServiceImpl implements UserSettingsService { - private final HttpClient client = HttpClient.newHttpClient(); - - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; + + @Inject + public UserSettingsServiceImpl(RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } - public CompletableFuture<Optional<UserSettings>> getUserSettings(String userId) { + public CompletableFuture<Result<UserSettings>> getUserSettings(String userId) { String routeAddress = "http://localhost:8080/api/v1/user-settings/user/" + userId; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); - 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.<UserSettings>empty(); - try { - UserSettings userSettings = JsonUtil.getObjectMapper().readValue(response.body(), UserSettings.class); - return Optional.of(userSettings); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<UserSettings>empty(); - } - }); + return requestHandler.sendRequest(request, new TypeReference<UserSettings>() {}); } - public CompletableFuture<Optional<UserSettings>> saveUserSettings(UpdateUserSettingsDTO userSettingsDTO) { + public CompletableFuture<Result<UserSettings>> saveUserSettings(UpdateUserSettingsDTO userSettingsDTO) { String routeAddress = "http://localhost:8080/api/v1/user-settings/update"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(userSettingsDTO); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .PUT(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) - .headers(HEADER_KEY, headerValue) - .headers("Content-Type", "application/json") - .build(); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), userSettingsDTO); - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.<UserSettings>empty(); - try { - UserSettings userSettings = JsonUtil.getObjectMapper().readValue(response.body(), UserSettings.class); - return Optional.of(userSettings); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<UserSettings>empty(); - } - }); + return requestHandler.sendRequest(request, new TypeReference<UserSettings>() {}); } } diff --git a/src/main/java/org/chainoptim/desktop/core/user/controller/PublicUsersSearchAndSelectionController.java b/src/main/java/org/chainoptim/desktop/core/user/controller/PublicUsersSearchAndSelectionController.java index b3e01fc2..c575dea3 100644 --- a/src/main/java/org/chainoptim/desktop/core/user/controller/PublicUsersSearchAndSelectionController.java +++ b/src/main/java/org/chainoptim/desktop/core/user/controller/PublicUsersSearchAndSelectionController.java @@ -2,6 +2,7 @@ import org.chainoptim.desktop.core.user.dto.UserSearchResultDTO; import org.chainoptim.desktop.core.user.service.UserService; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import com.google.inject.Inject; import javafx.application.Platform; @@ -91,12 +92,12 @@ private void searchForUsers() { .exceptionally(this::handleSearchException); } - private Optional<PaginatedResults<UserSearchResultDTO>> handleSearchResponse(Optional<PaginatedResults<UserSearchResultDTO>> optionalPaginatedResults) { + private Result<PaginatedResults<UserSearchResultDTO>> handleSearchResponse(Result<PaginatedResults<UserSearchResultDTO>> optionalPaginatedResults) { Platform.runLater(() -> { - if (optionalPaginatedResults.isEmpty()) { + if (optionalPaginatedResults.getError() != null) { return; } - PaginatedResults<UserSearchResultDTO> paginatedResults = optionalPaginatedResults.get(); + PaginatedResults<UserSearchResultDTO> paginatedResults = optionalPaginatedResults.getData(); totalCount = (int) paginatedResults.getTotalCount(); // Render Users List + Next Page Button @@ -129,9 +130,9 @@ private Optional<PaginatedResults<UserSearchResultDTO>> handleSearchResponse(Opt return optionalPaginatedResults; } - private Optional<PaginatedResults<UserSearchResultDTO>> handleSearchException(Throwable throwable) { + private Result<PaginatedResults<UserSearchResultDTO>> handleSearchException(Throwable throwable) { System.out.println("Error searching for users: " + throwable.getMessage()); - return Optional.empty(); + return new Result<>(); } private void selectUser(UserSearchResultDTO user) { diff --git a/src/main/java/org/chainoptim/desktop/core/user/controller/UsersListByCustomRoleController.java b/src/main/java/org/chainoptim/desktop/core/user/controller/UsersListByCustomRoleController.java index 2ac76b98..bbb7e54c 100644 --- a/src/main/java/org/chainoptim/desktop/core/user/controller/UsersListByCustomRoleController.java +++ b/src/main/java/org/chainoptim/desktop/core/user/controller/UsersListByCustomRoleController.java @@ -3,6 +3,7 @@ import org.chainoptim.desktop.core.organization.model.CustomRole; import org.chainoptim.desktop.core.user.model.User; import org.chainoptim.desktop.core.user.service.UserService; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; import com.google.inject.Inject; import javafx.application.Platform; @@ -36,12 +37,12 @@ public void setData(CustomRole data) { .exceptionally(this::handleUsersException); } - private Optional<List<User>> handleUsersResponse(Optional<List<User>> users) { - if (users.isEmpty()) { - return Optional.empty(); + private Result<List<User>> handleUsersResponse(Result<List<User>> users) { + if (users.getError() != null) { + return new Result<>(); } Platform.runLater(() -> { - List<User> userList = users.get(); + List<User> userList = users.getData(); usersVBox.getChildren().clear(); for (User user : userList) { @@ -53,9 +54,9 @@ private Optional<List<User>> handleUsersResponse(Optional<List<User>> users) { return users; } - private Optional<List<User>> handleUsersException(Throwable ex) { + private Result<List<User>> handleUsersException(Throwable ex) { ex.printStackTrace(); - return Optional.empty(); + return new Result<>(); } } 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 7d99a282..07ecdd51 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 @@ -2,6 +2,7 @@ import org.chainoptim.desktop.core.user.dto.UserSearchResultDTO; import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import java.util.List; @@ -10,11 +11,11 @@ public interface UserService { // Read - CompletableFuture<Optional<User>> getUserByUsername(String username); - CompletableFuture<Optional<List<User>>> getUsersByCustomRoleId(Integer customRoleId); - CompletableFuture<Optional<PaginatedResults<UserSearchResultDTO>>> searchPublicUsers(String searchQuery, int page, int itemsPerPage); + CompletableFuture<Result<User>> getUserByUsername(String username); + CompletableFuture<Result<List<User>>> getUsersByCustomRoleId(Integer customRoleId); + CompletableFuture<Result<PaginatedResults<UserSearchResultDTO>>> searchPublicUsers(String searchQuery, int page, int itemsPerPage); // Write - CompletableFuture<Optional<User>> assignBasicRoleToUser(String userId, User.Role role); - CompletableFuture<Optional<User>> assignCustomRoleToUser(String userId, Integer roleId); - CompletableFuture<Optional<User>> removeUserFromOrganization(String userId, Integer organizationId); + CompletableFuture<Result<User>> assignBasicRoleToUser(String userId, User.Role role); + CompletableFuture<Result<User>> assignCustomRoleToUser(String userId, Integer roleId); + CompletableFuture<Result<User>> removeUserFromOrganization(String userId, Integer organizationId); } diff --git a/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java b/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java index 76d02c1e..2e5fcfb3 100644 --- a/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/user/service/UserServiceImpl.java @@ -4,212 +4,88 @@ import org.chainoptim.desktop.core.user.dto.AssignCustomRoleDTO; import org.chainoptim.desktop.core.user.dto.UserSearchResultDTO; import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.shared.httphandling.HttpMethod; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.model.PaginatedResults; -import org.chainoptim.desktop.shared.util.JsonUtil; -import org.chainoptim.desktop.core.user.util.TokenManager; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.inject.Inject; -import java.net.HttpURLConnection; -import java.net.URI; import java.net.URLEncoder; -import java.net.http.HttpClient; import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; public class UserServiceImpl implements UserService { - private final HttpClient client = HttpClient.newHttpClient(); - - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; private static final String BASE_PATH = "http://localhost:8080/api/v1/users"; - public CompletableFuture<Optional<User>> getUserByUsername(String username) { + @Inject + public UserServiceImpl(RequestHandler requestHandler, RequestBuilder requestBuilder, TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } + + public CompletableFuture<Result<User>> getUserByUsername(String username) { String encodedUsername = URLEncoder.encode(username, StandardCharsets.UTF_8); String routeAddress = BASE_PATH + "/username/" + encodedUsername; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .headers(HEADER_KEY, headerValue) - .GET() - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() == HttpURLConnection.HTTP_OK) { - try { - User user = JsonUtil.getObjectMapper().readValue(response.body(), User.class); - return Optional.of(user); - } catch (Exception e) { - e.printStackTrace(); - } - } - return Optional.empty(); - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<User>() {}); } - public CompletableFuture<Optional<List<User>>> getUsersByCustomRoleId(Integer customRoleId) { + public CompletableFuture<Result<List<User>>> getUsersByCustomRoleId(Integer customRoleId) { String routeAddress = BASE_PATH + "/search/custom-role/" + customRoleId; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .headers(HEADER_KEY, headerValue) - .GET() - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) - return Optional.<List<User>>empty(); - - try { - List<User> users = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<List<User>>() {}); - return Optional.of(users); - } catch (Exception e) { - e.printStackTrace(); - } - - return Optional.<List<User>>empty(); - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<List<User>>() {}); } - public CompletableFuture<Optional<PaginatedResults<UserSearchResultDTO>>> searchPublicUsers(String searchQuery, int page, int itemsPerPage) { + public CompletableFuture<Result<PaginatedResults<UserSearchResultDTO>>> searchPublicUsers(String searchQuery, int page, int itemsPerPage) { String encodedSearchQuery = URLEncoder.encode(searchQuery, StandardCharsets.UTF_8); String routeAddress = BASE_PATH + "/search/public?searchQuery=" + encodedSearchQuery + "&page=" + page + "&itemsPerPage=" + itemsPerPage; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .headers(HEADER_KEY, headerValue) - .GET() - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.<PaginatedResults<UserSearchResultDTO>>empty(); - try { - PaginatedResults<UserSearchResultDTO> userSearchResultDTO = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<PaginatedResults<UserSearchResultDTO>>() {}); - return Optional.of(userSearchResultDTO); - } catch (Exception e) { - e.printStackTrace(); - } - return Optional.<PaginatedResults<UserSearchResultDTO>>empty(); - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<PaginatedResults<UserSearchResultDTO>>() {}); } // Write - public CompletableFuture<Optional<User>> assignBasicRoleToUser(String userId, User.Role role) { + public CompletableFuture<Result<User>> assignBasicRoleToUser(String userId, User.Role role) { String routeAddress = BASE_PATH + "/" + userId + "/assign-basic-role"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(new AssignBasicRoleDTO(role)); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .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()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - User user = JsonUtil.getObjectMapper().readValue(response.body(), User.class); - return Optional.of(user); - } catch (Exception e) { - e.printStackTrace(); - } - return Optional.empty(); - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), new AssignBasicRoleDTO(role)); + if (request == null) return requestHandler.getParsingErrorResult(); + + return requestHandler.sendRequest(request, new TypeReference<User>() {}); } - public CompletableFuture<Optional<User>> assignCustomRoleToUser(String userId, Integer roleId) { + public CompletableFuture<Result<User>> assignCustomRoleToUser(String userId, Integer roleId) { String routeAddress = BASE_PATH + "/" + userId + "/assign-custom-role"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(new AssignCustomRoleDTO(roleId)); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .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()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - User user = JsonUtil.getObjectMapper().readValue(response.body(), User.class); - return Optional.of(user); - } catch (Exception e) { - e.printStackTrace(); - } - return Optional.empty(); - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), new AssignCustomRoleDTO(roleId)); + + return requestHandler.sendRequest(request, new TypeReference<User>() {}); } - public CompletableFuture<Optional<User>> removeUserFromOrganization(String userId, Integer organizationId) { + public CompletableFuture<Result<User>> removeUserFromOrganization(String userId, Integer organizationId) { String routeAddress = BASE_PATH + "/" + userId + "/remove-from-organization/" + organizationId.toString(); - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .PUT(HttpRequest.BodyPublishers.noBody()) - .headers(HEADER_KEY, headerValue) - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.<User>empty(); - try { - User user = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<User>() {}); - return Optional.of(user); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<User>empty(); - } - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), null); + + return requestHandler.sendRequest(request, new TypeReference<User>() {}); } } diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java index aaf31ee1..0797eb96 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java @@ -7,6 +7,7 @@ import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; import org.chainoptim.desktop.shared.features.location.model.Location; import org.chainoptim.desktop.shared.features.location.service.LocationService; +import org.chainoptim.desktop.shared.httphandling.Result; import com.google.inject.Inject; import javafx.application.Platform; @@ -96,19 +97,19 @@ protected void updateItem(Location item, boolean empty) { .exceptionally(this::handleLocationsException); } - private Optional<List<Location>> handleLocationsResponse(Optional<List<Location>> locationsOptional) { + private Result<List<Location>> handleLocationsResponse(Result<List<Location>> result) { Platform.runLater(() -> { - if (locationsOptional.isEmpty()) { + if (result.getError() != null) { return; } - locationComboBox.getItems().setAll(locationsOptional.get()); + locationComboBox.getItems().setAll(result.getData()); }); - return locationsOptional; + return result; } - private Optional<List<Location>> handleLocationsException(Throwable ex) { - return Optional.empty(); + private Result<List<Location>> handleLocationsException(Throwable ex) { + return new Result<>(); } private void toggleVisibilityBasedOnSelection() { diff --git a/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationService.java b/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationService.java index a599e37f..0859de51 100644 --- a/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationService.java +++ b/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationService.java @@ -3,6 +3,7 @@ import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; import org.chainoptim.desktop.shared.features.location.dto.UpdateLocationDTO; import org.chainoptim.desktop.shared.features.location.model.Location; +import org.chainoptim.desktop.shared.httphandling.Result; import java.util.List; import java.util.Optional; @@ -10,8 +11,8 @@ public interface LocationService { - CompletableFuture<Optional<List<Location>>> getLocationsByOrganizationId(Integer organizationId); - CompletableFuture<Optional<Location>> createLocation(CreateLocationDTO locationDTO); - CompletableFuture<Optional<Location>> updateLocation(UpdateLocationDTO locationDTO); - CompletableFuture<Optional<Integer>> deleteLocation(Integer id); + CompletableFuture<Result<List<Location>>> getLocationsByOrganizationId(Integer organizationId); + CompletableFuture<Result<Location>> createLocation(CreateLocationDTO locationDTO); + CompletableFuture<Result<Location>> updateLocation(UpdateLocationDTO locationDTO); + CompletableFuture<Result<Integer>> deleteLocation(Integer id); } diff --git a/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationServiceImpl.java b/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationServiceImpl.java index 9d5f7154..7ad0e37c 100644 --- a/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/shared/features/location/service/LocationServiceImpl.java @@ -1,151 +1,73 @@ package org.chainoptim.desktop.shared.features.location.service; -import com.fasterxml.jackson.core.type.TypeReference; -import org.chainoptim.desktop.core.user.util.TokenManager; -import org.chainoptim.desktop.features.product.dto.CreateUnitOfMeasurementDTO; -import org.chainoptim.desktop.features.product.dto.UpdateUnitOfMeasurementDTO; -import org.chainoptim.desktop.features.product.model.UnitOfMeasurement; +import org.chainoptim.desktop.core.user.service.TokenManager; import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; import org.chainoptim.desktop.shared.features.location.dto.UpdateLocationDTO; import org.chainoptim.desktop.shared.features.location.model.Location; -import org.chainoptim.desktop.shared.util.JsonUtil; +import org.chainoptim.desktop.shared.httphandling.HttpMethod; +import org.chainoptim.desktop.shared.httphandling.RequestBuilder; +import org.chainoptim.desktop.shared.httphandling.RequestHandler; +import org.chainoptim.desktop.shared.httphandling.Result; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.inject.Inject; -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.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; public class LocationServiceImpl implements LocationService { - private final HttpClient client = HttpClient.newHttpClient(); - - private static final String HEADER_KEY = "Authorization"; - private static final String HEADER_VALUE_PREFIX = "Bearer "; + private final RequestHandler requestHandler; + private final RequestBuilder requestBuilder; + private final TokenManager tokenManager; + + @Inject + public LocationServiceImpl(RequestHandler requestHandler, + RequestBuilder requestBuilder, + TokenManager tokenManager) { + this.requestHandler = requestHandler; + this.requestBuilder = requestBuilder; + this.tokenManager = tokenManager; + } // Fetch - public CompletableFuture<Optional<List<Location>>> getLocationsByOrganizationId(Integer organizationId) { + public CompletableFuture<Result<List<Location>>> getLocationsByOrganizationId(Integer organizationId) { String routeAddress = "http://localhost:8080/api/v1/locations/organization/" + organizationId.toString(); - 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.<List<Location>>empty(); - try { - List<Location> locations = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<List<Location>>() {}); - return Optional.of(locations); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<List<Location>>empty(); - } - }); + HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); + + return requestHandler.sendRequest(request, new TypeReference<List<Location>>() {}); } // Create - public CompletableFuture<Optional<Location>> createLocation(CreateLocationDTO locationDTO) { + public CompletableFuture<Result<Location>> createLocation(CreateLocationDTO locationDTO) { String routeAddress = "http://localhost:8080/api/v1/locations/create"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(locationDTO); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) - .headers(HEADER_KEY, headerValue) - .headers("Content-Type", "application/json") - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - Location location = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<Location>() {}); - return Optional.of(location); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<Location>empty(); - } - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.POST, routeAddress, tokenManager.getToken(), locationDTO); + + return requestHandler.sendRequest(request, new TypeReference<Location>() {}); } // Update - public CompletableFuture<Optional<Location>> updateLocation(UpdateLocationDTO locationDTO) { + public CompletableFuture<Result<Location>> updateLocation(UpdateLocationDTO locationDTO) { String routeAddress = "http://localhost:8080/api/v1/locations/update"; - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - // Serialize DTO - String requestBody = null; - try { - requestBody = JsonUtil.getObjectMapper().writeValueAsString(locationDTO); - } catch (Exception e) { - e.printStackTrace(); - } - assert requestBody != null; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) - .headers(HEADER_KEY, headerValue) - .headers("Content-Type", "application/json") - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - try { - Location location = JsonUtil.getObjectMapper().readValue(response.body(), new TypeReference<Location>() {}); - return Optional.of(location); - } catch (Exception e) { - e.printStackTrace(); - return Optional.<Location>empty(); - } - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.PUT, routeAddress, tokenManager.getToken(), locationDTO); + + return requestHandler.sendRequest(request, new TypeReference<Location>() {}); } // Delete - public CompletableFuture<Optional<Integer>> deleteLocation(Integer id) { + public CompletableFuture<Result<Integer>> deleteLocation(Integer id) { String routeAddress = "http://localhost:8080/api/v1/locations/delete/" + id.toString(); - String jwtToken = TokenManager.getToken(); - if (jwtToken == null) return new CompletableFuture<>(); - String headerValue = HEADER_VALUE_PREFIX + jwtToken; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(routeAddress)) - .DELETE() - .headers(HEADER_KEY, headerValue) - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(response -> { - if (response.statusCode() != HttpURLConnection.HTTP_OK) return Optional.empty(); - return Optional.of(id); - }); + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.DELETE, routeAddress, tokenManager.getToken(), null); + + return requestHandler.sendRequest(request, new TypeReference<Integer>() {}); } } From 61e358390ee30b625d7b5ee5d40d83a1e1f29ad0 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:12:21 +0300 Subject: [PATCH 02/10] Make small UI adjustments --- .../NotificationSettingsController.java | 4 ++ .../controller/ProductsController.java | 30 --------------- .../common/uielements/settings/Switch.java | 38 +++++++++++++++++++ src/main/resources/css/buttons.css | 5 +++ src/main/resources/css/entity-card.css | 5 ++- src/main/resources/css/entity-page.css | 5 +-- src/main/resources/css/factory-production.css | 3 ++ src/main/resources/css/list-header.css | 1 + src/main/resources/css/settings.css | 28 +++++++++++++- src/main/resources/css/sidebar.css | 2 +- .../settings/NotificationSettingsView.fxml | 2 + .../features/product/ProductsView.fxml | 7 +--- 12 files changed, 87 insertions(+), 43 deletions(-) create mode 100644 src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java index ed39511a..e10f6932 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.core.settings.controller; import org.chainoptim.desktop.core.settings.model.UserSettings; +import org.chainoptim.desktop.shared.common.uielements.settings.Switch; import org.chainoptim.desktop.shared.util.DataReceiver; import javafx.beans.property.BooleanProperty; @@ -38,12 +39,15 @@ public class NotificationSettingsController implements DataReceiver<UserSettings // FXML @FXML private VBox contentVBox; + @FXML + private Switch overallSwitch; private final ToggleButton toggleOverallButton = new ToggleButton(); private final Map<String, ToggleButton> featureToggleButtons = new HashMap<>(); @Override public void setData(UserSettings userSettings) { this.userSettings = userSettings; + overallSwitch.initializeSwitch(aggregateNotificationSettings()); initializeUI(); } 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 f5691d48..b3fb7f4a 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 @@ -8,14 +8,11 @@ import org.chainoptim.desktop.features.product.model.Product; import org.chainoptim.desktop.features.product.service.ProductService; import org.chainoptim.desktop.shared.enums.Feature; -import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.search.controller.PageSelectorController; import org.chainoptim.desktop.shared.search.model.PaginatedResults; import org.chainoptim.desktop.shared.search.model.SearchParams; -import org.chainoptim.desktop.shared.toast.controller.ToastManager; -import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; @@ -36,7 +33,6 @@ public class ProductsController implements Initializable { private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; - private final ToastManager toastManager; // State private final FallbackManager fallbackManager; @@ -68,7 +64,6 @@ public ProductsController(ProductService productService, NavigationService navigationService, CurrentSelectionService currentSelectionService, CommonViewsLoader commonViewsLoader, - ToastManager toastManager, FallbackManager fallbackManager, SearchParams searchParams ) { @@ -76,35 +71,10 @@ public ProductsController(ProductService productService, this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; - this.toastManager = toastManager; this.fallbackManager = fallbackManager; this.searchParams = searchParams; } - @FXML - private void addToast() { - ToastInfo toastInfo = new ToastInfo("Success", "The product has been created successfully", OperationOutcome.SUCCESS); - toastManager.addToast(toastInfo); - } - - @FXML - private void addToastError() { - ToastInfo toastInfo = new ToastInfo("Error", "There was an error creating the product", OperationOutcome.ERROR); - toastManager.addToast(toastInfo); - } - - @FXML - private void addToastInfo() { - ToastInfo toastInfo = new ToastInfo("Info", "A Product is any good manufactured by your organization", OperationOutcome.INFO); - toastManager.addToast(toastInfo); - } - - @FXML - private void addToastWarning() { - ToastInfo toastInfo = new ToastInfo("Warning", "You are about to delete a product", OperationOutcome.WARNING); - toastManager.addToast(toastInfo); - } - @Override public void initialize(URL location, ResourceBundle resources) { headerController = commonViewsLoader.loadListHeader(headerContainer); diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java new file mode 100644 index 00000000..c688797d --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java @@ -0,0 +1,38 @@ +package org.chainoptim.desktop.shared.common.uielements.settings; + +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import lombok.Getter; + +@Getter +public class Switch extends HBox { + + private boolean isOn; + + private Button leftButton; + private Button rightButton; + + public void initializeSwitch(boolean isOn) { + this.getStyleClass().setAll("custom-switch"); + this.setMaxWidth(Region.USE_PREF_SIZE); + + leftButton = new Button("On"); + leftButton.setStyle("-fx-border-radius: 5 0 0 5; -fx-background-radius: 5 0 0 5; -fx-border-width: 1 0 1 1;"); + leftButton.setOnAction(event -> toggle(true)); + + rightButton = new Button("Off"); + rightButton.setStyle("-fx-border-radius: 0 5 5 0; -fx-background-radius: 0 5 5 0; -fx-border-width: 1 1 1 0;"); + rightButton.setOnAction(event -> toggle(false)); + + toggle(isOn); + + this.getChildren().addAll(leftButton, rightButton); + } + + private void toggle(boolean isOn) { + leftButton.getStyleClass().setAll(isOn ? "switch-select-item-selected" : "switch-select-item"); + rightButton.getStyleClass().setAll(isOn ? "switch-select-item" : "switch-select-item-selected"); + this.isOn = isOn; + } +} diff --git a/src/main/resources/css/buttons.css b/src/main/resources/css/buttons.css index cc1a126e..77b7580d 100644 --- a/src/main/resources/css/buttons.css +++ b/src/main/resources/css/buttons.css @@ -10,6 +10,7 @@ -fx-font-size: 14px; -fx-graphic-text-gap: 8px; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .standard-write-button:hover { @@ -39,6 +40,7 @@ -fx-font-size: 13px; -fx-graphic-text-gap: 8px; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 5, 0, 0, 2); } .standard-action-button:hover { @@ -57,6 +59,7 @@ -fx-font-size: 14px; -fx-graphic-text-gap: 8px; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .standard-delete-button:hover { @@ -75,6 +78,7 @@ -fx-font-size: 14px; -fx-graphic-text-gap: 8px; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .standard-cancel-button:hover { @@ -116,6 +120,7 @@ -fx-border-radius: 6px; -fx-graphic-text-gap: 8px; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .cancel-edit-button:hover { diff --git a/src/main/resources/css/entity-card.css b/src/main/resources/css/entity-card.css index 02d45246..8fc1cbe9 100644 --- a/src/main/resources/css/entity-card.css +++ b/src/main/resources/css/entity-card.css @@ -1,9 +1,10 @@ .entity-card { - -fx-background-color: #f1f2f2; - -fx-border-color: #bdbfad; + -fx-background-color: #f2f2f2; + -fx-border-color: #cdcdcd; -fx-border-width: 1px; -fx-border-radius: 5px; -fx-padding: 8px 12px; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.05), 5, 0, 0, 2); } .entity-card:hover { diff --git a/src/main/resources/css/entity-page.css b/src/main/resources/css/entity-page.css index 6d478118..b42b3bf8 100644 --- a/src/main/resources/css/entity-page.css +++ b/src/main/resources/css/entity-page.css @@ -1,9 +1,8 @@ - - .entity-header-container { -fx-padding: 20; - -fx-background-color: #eeeeee; + -fx-background-color: #f2f2f2; -fx-border-color: #c0c0c0 #ffffff #c0c0c0 #ffffff; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 4, 0, 0, 2); } .entity-page-title { diff --git a/src/main/resources/css/factory-production.css b/src/main/resources/css/factory-production.css index 7e2d47ca..af89eaf0 100644 --- a/src/main/resources/css/factory-production.css +++ b/src/main/resources/css/factory-production.css @@ -44,6 +44,7 @@ -fx-pref-width: 24px; -fx-background-color: #0077C7; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .add-button:hover { @@ -55,6 +56,7 @@ -fx-pref-width: 24px; -fx-background-color: #00A062; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .update-button:hover { @@ -66,6 +68,7 @@ -fx-pref-width: 24px; -fx-background-color: #B71011; -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .delete-button:hover { diff --git a/src/main/resources/css/list-header.css b/src/main/resources/css/list-header.css index 3181a175..621a6a87 100644 --- a/src/main/resources/css/list-header.css +++ b/src/main/resources/css/list-header.css @@ -2,6 +2,7 @@ -fx-background-color: #ffffff; -fx-spacing: 20px; -fx-border-color: #c0c0c0; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } .toolbar { diff --git a/src/main/resources/css/settings.css b/src/main/resources/css/settings.css index d44d4e3e..807c08bd 100644 --- a/src/main/resources/css/settings.css +++ b/src/main/resources/css/settings.css @@ -26,4 +26,30 @@ .settings-label { -fx-font-size: 14px; -fx-font-weight: bold; -} \ No newline at end of file +} + +.custom-switch { + -fx-background-radius: 5; + -fx-border-radius: 5; + -fx-border-color: #c0c0c0; + -fx-border-width: 1; +} + +.switch-select-item { + -fx-padding: 4px 8px; + -fx-background-color: #efeeef; + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-alignment: center; + -fx-cursor: hand; +} + +.switch-select-item-selected { + -fx-padding: 4px 8px; + -fx-background-color: #006AEE; + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-alignment: center; + -fx-cursor: hand; +} diff --git a/src/main/resources/css/sidebar.css b/src/main/resources/css/sidebar.css index ecc8c292..8b019b40 100644 --- a/src/main/resources/css/sidebar.css +++ b/src/main/resources/css/sidebar.css @@ -3,7 +3,7 @@ -fx-background-radius: 0px 6px 6px 0px; -fx-border-color: #333; -fx-border-radius: 0px 6px 6px 0px; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.16), 5, 0, 0, 2); + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.25), 5, 0, 0, 2); } .sidebar-inner-container { diff --git a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml index d8066a6e..63486d26 100644 --- a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> +<?import org.chainoptim.desktop.shared.common.uielements.settings.Switch?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.core.settings.controller.NotificationSettingsController" prefHeight="400.0" prefWidth="600.0"> + <Switch fx:id="overallSwitch" /> <VBox fx:id="contentVBox" styleClass="settings-tab-container"> <!-- Overall Settings --> diff --git a/src/main/resources/org/chainoptim/desktop/features/product/ProductsView.fxml b/src/main/resources/org/chainoptim/desktop/features/product/ProductsView.fxml index 5909d2c2..76b16b4a 100644 --- a/src/main/resources/org/chainoptim/desktop/features/product/ProductsView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/product/ProductsView.fxml @@ -1,18 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> - <?import javafx.scene.control.ScrollPane?> -<?import javafx.scene.control.Button?> + <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" styleClass="main-container" fx:controller="org.chainoptim.desktop.features.product.controller.ProductsController"> <StackPane fx:id="headerContainer"/> - <Button onAction="#addToast"/> - <Button onAction="#addToastError"/> - <Button onAction="#addToastInfo"/> - <Button onAction="#addToastWarning"/> <StackPane VBox.vgrow="ALWAYS"> <ScrollPane fx:id="productsScrollPane" VBox.vgrow="ALWAYS" fitToWidth="true" fitToHeight="true"> From a8c4188c9a463255dbbf3ad69a31420c9a12c673 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:18:18 +0300 Subject: [PATCH 03/10] Switch to a third-party ToggleButton --- pom.xml | 5 ++ .../controller/GeneralSettingsController.java | 2 +- .../NotificationSettingsController.java | 66 ++++++++++--------- .../uielements/settings/EnumSelector.java | 41 ++++++++++-- .../common/uielements/settings/Switch.java | 28 +++++--- src/main/resources/css/buttons.css | 3 +- src/main/resources/css/common-elements.css | 34 ++++++++-- src/main/resources/css/settings.css | 49 ++++++++------ .../settings/NotificationSettingsView.fxml | 2 - 9 files changed, 151 insertions(+), 79 deletions(-) diff --git a/pom.xml b/pom.xml index 73071dc0..3f7175a6 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,11 @@ </exclusion> </exclusions> </dependency> + <dependency> + <groupId>com.jfoenix</groupId> + <artifactId>jfoenix</artifactId> + <version>9.0.10</version> + </dependency> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/GeneralSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/GeneralSettingsController.java index 2b2ce004..4042ac0b 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/GeneralSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/GeneralSettingsController.java @@ -47,6 +47,6 @@ private void setUpListeners() { } public void cancelChanges(UserSettings originalUserSettings) { - infoLevelSelector.selectValue(originalUserSettings.getGeneralSettings().getInfoLevel()); + infoLevelSelector.selectValue(originalUserSettings.getGeneralSettings().getInfoLevel(), InfoLevel.class); } } diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java index e10f6932..9ed7898a 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java @@ -1,20 +1,21 @@ package org.chainoptim.desktop.core.settings.controller; import org.chainoptim.desktop.core.settings.model.UserSettings; -import org.chainoptim.desktop.shared.common.uielements.settings.Switch; import org.chainoptim.desktop.shared.util.DataReceiver; +import com.jfoenix.controls.JFXToggleButton; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; +import javafx.geometry.Pos; import javafx.scene.control.Label; -import javafx.scene.control.ToggleButton; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import lombok.Setter; import java.util.HashMap; @@ -39,21 +40,18 @@ public class NotificationSettingsController implements DataReceiver<UserSettings // FXML @FXML private VBox contentVBox; - @FXML - private Switch overallSwitch; - private final ToggleButton toggleOverallButton = new ToggleButton(); - private final Map<String, ToggleButton> featureToggleButtons = new HashMap<>(); + private JFXToggleButton overallToggleButton = new JFXToggleButton(); + private final Map<String, JFXToggleButton> featureToggleButtons = new HashMap<>(); @Override public void setData(UserSettings userSettings) { this.userSettings = userSettings; - overallSwitch.initializeSwitch(aggregateNotificationSettings()); initializeUI(); } private void initializeUI() { contentVBox.getChildren().clear(); - contentVBox.setSpacing(10); + contentVBox.setSpacing(12); cleanUpGlobalListeners(); @@ -72,7 +70,7 @@ private void cleanUpGlobalListeners() { haveSettingsChanged.removeListener(hasChangedListener); } if (overallChangeListener != null) { - toggleOverallButton.selectedProperty().removeListener(overallChangeListener); + overallToggleButton.selectedProperty().removeListener(overallChangeListener); } } @@ -87,9 +85,9 @@ private void setUpGlobalListeners() { // Overall change listener overallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - handleToggleOverallButton(userSettings); + handleToggleOverallSwitch(); }; - toggleOverallButton.selectedProperty().addListener(overallChangeListener); + overallToggleButton.selectedProperty().addListener(overallChangeListener); } private void renderOverallHBox() { @@ -102,17 +100,18 @@ private void renderOverallHBox() { overallHBox.getChildren().add(region); HBox.setHgrow(region, Priority.ALWAYS); - toggleOverallButton.getStyleClass().setAll("toggle-button"); + overallToggleButton = new JFXToggleButton(); boolean overallSetting = aggregateNotificationSettings(); - toggleOverallButton.setText(overallSetting ? "On" : "Off"); - toggleOverallButton.setSelected(overallSetting); - overallHBox.getChildren().add(toggleOverallButton); + overallToggleButton.setSelected(overallSetting); + styleToggleButton(overallToggleButton); + overallHBox.getChildren().add(overallToggleButton); contentVBox.getChildren().add(overallHBox); } private void renderFeatureHBox(String feature) { HBox featureHBox = new HBox(); + featureHBox.setAlignment(Pos.CENTER_LEFT); Label featureLabel = new Label(feature); featureLabel.getStyleClass().add("settings-label"); featureHBox.getChildren().add(featureLabel); @@ -121,40 +120,35 @@ private void renderFeatureHBox(String feature) { featureHBox.getChildren().add(region); HBox.setHgrow(region, Priority.ALWAYS); - ToggleButton toggleButton = new ToggleButton(); + JFXToggleButton toggleButton = new JFXToggleButton(); boolean featureSetting = getFeatureSetting(feature); - toggleButton.setText(featureSetting ? "On" : "Off"); toggleButton.setSelected(featureSetting); - toggleButton.getStyleClass().setAll("toggle-button"); + styleToggleButton(toggleButton); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> - handleToggleFeatureButton(userSettings, toggleButton, feature, toggleButton.isSelected())); + handleToggleFeatureSwitch(featureToggleButtons.get(feature), feature, newValue)); featureToggleButtons.put(feature, toggleButton); featureHBox.getChildren().add(toggleButton); contentVBox.getChildren().add(featureHBox); } - private void handleToggleOverallButton(UserSettings userSettings) { - if (toggleOverallButton.isSelected()) { - toggleOverallButton.setText("On"); - } else { - toggleOverallButton.setText("Off"); - } + private void handleToggleOverallSwitch() { + boolean newState = overallToggleButton.isSelected(); for (String feature : notificationFeatures) { - ToggleButton toggleButton = featureToggleButtons.get(feature); - handleToggleFeatureButton(userSettings, toggleButton, feature, toggleOverallButton.isSelected()); + JFXToggleButton featureToggleButton = featureToggleButtons.get(feature); + handleToggleFeatureSwitch(featureToggleButton, feature, newState); } } - private void handleToggleFeatureButton(UserSettings userSettings, ToggleButton toggleButton, String feature, Boolean isOn) { - toggleButton.setSelected(isOn); - toggleButton.setText(Boolean.TRUE.equals(isOn) ? "On" : "Off"); + private void handleToggleFeatureSwitch(JFXToggleButton featureToggleButton, String feature, Boolean isOn) { + featureToggleButton.setSelected(isOn); haveSettingsChanged.setValue(true); + System.out.println("Settings changed: " + haveSettingsChanged); setFeatureSetting(feature, isOn); } public void cancelChanges(UserSettings originalUserSettings) { - toggleOverallButton.selectedProperty().removeListener(overallChangeListener); + overallToggleButton.selectedProperty().removeListener(overallChangeListener); setData(originalUserSettings); haveSettingsChanged.setValue(false); } @@ -186,4 +180,14 @@ private void setFeatureSetting(String feature, boolean isOn) { default -> {} } } + + private void styleToggleButton(JFXToggleButton toggleButton) { + toggleButton.setStyle("-fx-background-color: transparent; -fx-border-width: 0; -fx-border-color: transparent;"); + toggleButton.setUnToggleColor(Color.valueOf("#cccccc")); + toggleButton.setToggleColor(Color.valueOf("#006AEE")); + toggleButton.setToggleLineColor(Color.valueOf("#337BEF")); + toggleButton.setBorder(null); + toggleButton.setDisableVisualFocus(true); + toggleButton.setMinSize(50, 30); + } } diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java index 2d00fe37..671b0f7d 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java @@ -5,8 +5,11 @@ import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class EnumSelector<T extends Enum<T>> extends HBox { @@ -18,28 +21,52 @@ public EnumSelector() { super(); this.setAlignment(Pos.CENTER_LEFT); + this.setMaxWidth(Region.USE_PREF_SIZE); + this.getStyleClass().setAll("enum-selector"); } public void initializeSelector(Class<T> enumType, T selectedValue) { if (selectedValue == null) return; this.selectedValueProperty.setValue(selectedValue); + int index = 0; + for (T value : enumType.getEnumConstants()) { // Create a button for each enum value Button button = new Button(value.toString()); - button.getStyleClass().setAll( - selectedValue.equals(value) ? "select-item-selected" : "select-item" - ); - button.setOnAction(event -> selectValue(value)); + + styleButton(selectedValue, value, button, index, enumType); + + button.setOnAction(event -> selectValue(value, enumType)); + this.getChildren().add(button); buttons.put(value, button); + + index++; } } - public void selectValue(T value) { + private void styleButton(T selectedValue, T value, Button button, int index, Class<T> enumType) { + List<String> styles = new ArrayList<>(); + styles.add(selectedValue.equals(value) ? "enum-select-item-selected" : "enum-select-item"); + if (index == 0) { + styles.add("enum-left-item"); + } + if (index == enumType.getEnumConstants().length - 1) { + styles.add("enum-right-item"); + } + button.getStyleClass().setAll(styles); + } + + public void selectValue(T value, Class<T> enumType) { selectedValueProperty.setValue(value); - buttons.forEach((k, v) -> v.getStyleClass().setAll( - k.equals(selectedValueProperty.getValue()) ? "select-item-selected" : "select-item" + int index = 0; + for (Map.Entry<T, Button> entry : buttons.entrySet()) { + styleButton(value, entry.getKey(), entry.getValue(), index, enumType); + index++; + } + buttons.forEach((k, v) -> v.getStyleClass().add( + k.equals(selectedValueProperty.getValue()) ? "enum-select-item-selected" : "enum-select-item" )); } diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java index c688797d..5452578f 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/Switch.java @@ -1,28 +1,32 @@ package org.chainoptim.desktop.shared.common.uielements.settings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.control.Button; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import lombok.Getter; +import java.util.ArrayList; +import java.util.List; + @Getter public class Switch extends HBox { - private boolean isOn; + private BooleanProperty isOn = new SimpleBooleanProperty(); private Button leftButton; private Button rightButton; public void initializeSwitch(boolean isOn) { - this.getStyleClass().setAll("custom-switch"); + this.getStyleClass().setAll("enum-selector"); this.setMaxWidth(Region.USE_PREF_SIZE); + this.setMaxHeight(Region.USE_PREF_SIZE); leftButton = new Button("On"); - leftButton.setStyle("-fx-border-radius: 5 0 0 5; -fx-background-radius: 5 0 0 5; -fx-border-width: 1 0 1 1;"); leftButton.setOnAction(event -> toggle(true)); rightButton = new Button("Off"); - rightButton.setStyle("-fx-border-radius: 0 5 5 0; -fx-background-radius: 0 5 5 0; -fx-border-width: 1 1 1 0;"); rightButton.setOnAction(event -> toggle(false)); toggle(isOn); @@ -30,9 +34,17 @@ public void initializeSwitch(boolean isOn) { this.getChildren().addAll(leftButton, rightButton); } - private void toggle(boolean isOn) { - leftButton.getStyleClass().setAll(isOn ? "switch-select-item-selected" : "switch-select-item"); - rightButton.getStyleClass().setAll(isOn ? "switch-select-item" : "switch-select-item-selected"); - this.isOn = isOn; + public void toggle(boolean isOn) { + leftButton.getStyleClass().clear(); + leftButton.getStyleClass().add(isOn ? "enum-select-item-selected" : "enum-select-item"); + leftButton.getStyleClass().add("enum-left-item"); + rightButton.getStyleClass().clear(); + rightButton.getStyleClass().add(isOn ? "enum-select-item" : "enum-select-item-selected"); + rightButton.getStyleClass().add("enum-right-item"); + this.isOn.setValue(isOn); + } + + public BooleanProperty getIsOnProperty() { + return isOn; } } diff --git a/src/main/resources/css/buttons.css b/src/main/resources/css/buttons.css index 77b7580d..1f693949 100644 --- a/src/main/resources/css/buttons.css +++ b/src/main/resources/css/buttons.css @@ -33,7 +33,7 @@ .standard-action-button { -fx-padding: 4px 8px; -fx-background-color: #eeeeee; - -fx-border-color: #d0d0d0; + -fx-border-color: #dcdcdc; -fx-background-radius: 4; -fx-border-radius: 4; -fx-font-weight: 600; @@ -71,7 +71,6 @@ -fx-max-height: 32px; -fx-padding: 4px 12px; -fx-background-color: #eeeeee; - -fx-border-color: #d0d0d0; -fx-background-radius: 4; -fx-border-radius: 4; -fx-font-weight: 500; diff --git a/src/main/resources/css/common-elements.css b/src/main/resources/css/common-elements.css index 4a90d563..4d9d59ed 100644 --- a/src/main/resources/css/common-elements.css +++ b/src/main/resources/css/common-elements.css @@ -105,21 +105,41 @@ -fx-fill: #EAEAEA; } -.select-item { - -fx-padding: 6px 12px; - -fx-background-color: #ffffff; - -fx-font-size: 16px; +.enum-selector { + -fx-background-radius: 5; + -fx-border-radius: 5; + -fx-border-color: #cccccc; + -fx-border-width: 1; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 5, 0, 0, 2); +} + +.enum-select-item { + -fx-padding: 6px 9px; + -fx-background-color: #efeeef; + -fx-font-size: 14px; -fx-font-weight: bold; -fx-alignment: center; -fx-cursor: hand; } -.select-item-selected { - -fx-padding: 6px 12px; +.enum-select-item-selected { + -fx-padding: 6px 9px; -fx-background-color: #006AEE; -fx-text-fill: white; - -fx-font-size: 16px; + -fx-font-size: 14px; -fx-font-weight: bold; -fx-alignment: center; -fx-cursor: hand; +} + +.enum-left-item { + -fx-padding: 6px 9px 6px 12px; + -fx-border-radius: 5 0 0 5; + -fx-background-radius: 5 0 0 5; +} + +.enum-right-item { + -fx-padding: 6px 12px 6px 9px; + -fx-border-radius: 0 5 5 0; + -fx-background-radius: 0 5 5 0; } \ No newline at end of file diff --git a/src/main/resources/css/settings.css b/src/main/resources/css/settings.css index 807c08bd..d0b95746 100644 --- a/src/main/resources/css/settings.css +++ b/src/main/resources/css/settings.css @@ -18,7 +18,7 @@ } .settings-section-label { - -fx-font-size: 18px; + -fx-font-size: 20px; -fx-font-weight: bold; -fx-padding: 10 0 10 0; } @@ -28,28 +28,35 @@ -fx-font-weight: bold; } -.custom-switch { - -fx-background-radius: 5; - -fx-border-radius: 5; - -fx-border-color: #c0c0c0; - -fx-border-width: 1; +/* Switch */ +/* Style for the toggle button */ +/* General styling for the toggle button */ +.jfx-toggle-button { + -fx-padding: 0; } -.switch-select-item { - -fx-padding: 4px 8px; - -fx-background-color: #efeeef; - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-alignment: center; - -fx-cursor: hand; +/* Specific styling for the sliding thumb and track in both states */ +.jfx-toggle-button .jfx-toggle-node { + -fx-background-radius: 15; /* Optional: Adjust as per your design needs */ + -fx-background-color: transparent; /* Makes background transparent */ } -.switch-select-item-selected { - -fx-padding: 4px 8px; - -fx-background-color: #006AEE; - -fx-text-fill: white; - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-alignment: center; - -fx-cursor: hand; +.jfx-toggle-button:selected .jfx-toggle-node { + -fx-background-color: #006AEE; /* Your specified blue color for "On" state */ +} + +.jfx-toggle-button .jfx-rippler { + -fx-rippler-fill: transparent; /* Remove the ripple effect on click */ +} + +.jfx-toggle-button:selected .jfx-rippler { + -fx-rippler-fill: #006AEE; /* Consistent with the toggle's "On" state color */ +} + +.jfx-toggle-button .jfx-toggle-color { + -fx-background-color: #cce4ff; /* Lighter blue for the track */ +} + +.jfx-toggle-button:selected .jfx-toggle-color { + -fx-background-color: #006AEE; /* ON state color for the track */ } diff --git a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml index 63486d26..d8066a6e 100644 --- a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> -<?import org.chainoptim.desktop.shared.common.uielements.settings.Switch?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.core.settings.controller.NotificationSettingsController" prefHeight="400.0" prefWidth="600.0"> - <Switch fx:id="overallSwitch" /> <VBox fx:id="contentVBox" styleClass="settings-tab-container"> <!-- Overall Settings --> From 88c8dae2d097807fe2720e0191da03e0e7f2f725 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:13:44 +0300 Subject: [PATCH 04/10] Fix User Settings commit and cancel issues --- .../NotificationSettingsController.java | 121 +++++++++++------- .../controller/SettingsController.java | 7 +- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java index 9ed7898a..5247d2be 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java @@ -32,7 +32,7 @@ public class NotificationSettingsController implements DataReceiver<UserSettings @Setter private SettingsListener settingsListener; private ChangeListener<Boolean> hasChangedListener; - private ChangeListener<Boolean> overallChangeListener; + private ChangeListener<Boolean> notificationOverallChangeListener; // Constants private static final List<String> notificationFeatures = List.of("Supplier Orders", "Client Orders", "Factory Inventory", "Warehouse Inventory"); @@ -40,8 +40,8 @@ public class NotificationSettingsController implements DataReceiver<UserSettings // FXML @FXML private VBox contentVBox; - private JFXToggleButton overallToggleButton = new JFXToggleButton(); - private final Map<String, JFXToggleButton> featureToggleButtons = new HashMap<>(); + private JFXToggleButton notificationOverallToggleButton = new JFXToggleButton(); + private final Map<String, JFXToggleButton> notificationFeatureToggleButtons = new HashMap<>(); @Override public void setData(UserSettings userSettings) { @@ -53,8 +53,6 @@ private void initializeUI() { contentVBox.getChildren().clear(); contentVBox.setSpacing(12); - cleanUpGlobalListeners(); - renderOverallHBox(); for (String feature : notificationFeatures) { @@ -64,32 +62,6 @@ private void initializeUI() { setUpGlobalListeners(); } - private void cleanUpGlobalListeners() { - haveSettingsChanged.setValue(false); - if (hasChangedListener != null) { - haveSettingsChanged.removeListener(hasChangedListener); - } - if (overallChangeListener != null) { - overallToggleButton.selectedProperty().removeListener(overallChangeListener); - } - } - - private void setUpGlobalListeners() { - // Any change listener - hasChangedListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - if (settingsListener != null) { - settingsListener.handleSettingsChanged(newValue); - } - }; - haveSettingsChanged.addListener(hasChangedListener); - - // Overall change listener - overallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - handleToggleOverallSwitch(); - }; - overallToggleButton.selectedProperty().addListener(overallChangeListener); - } - private void renderOverallHBox() { HBox overallHBox = new HBox(); Label overallLabel = new Label("Overall"); @@ -100,11 +72,11 @@ private void renderOverallHBox() { overallHBox.getChildren().add(region); HBox.setHgrow(region, Priority.ALWAYS); - overallToggleButton = new JFXToggleButton(); + notificationOverallToggleButton = new JFXToggleButton(); boolean overallSetting = aggregateNotificationSettings(); - overallToggleButton.setSelected(overallSetting); - styleToggleButton(overallToggleButton); - overallHBox.getChildren().add(overallToggleButton); + notificationOverallToggleButton.setSelected(overallSetting); + styleToggleButton(notificationOverallToggleButton); + overallHBox.getChildren().add(notificationOverallToggleButton); contentVBox.getChildren().add(overallHBox); } @@ -121,21 +93,21 @@ private void renderFeatureHBox(String feature) { HBox.setHgrow(region, Priority.ALWAYS); JFXToggleButton toggleButton = new JFXToggleButton(); - boolean featureSetting = getFeatureSetting(feature); + boolean featureSetting = getNotificationFeatureSetting(feature); toggleButton.setSelected(featureSetting); styleToggleButton(toggleButton); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> - handleToggleFeatureSwitch(featureToggleButtons.get(feature), feature, newValue)); - featureToggleButtons.put(feature, toggleButton); + handleToggleFeatureSwitch(notificationFeatureToggleButtons.get(feature), feature, newValue)); + notificationFeatureToggleButtons.put(feature, toggleButton); featureHBox.getChildren().add(toggleButton); contentVBox.getChildren().add(featureHBox); } - private void handleToggleOverallSwitch() { - boolean newState = overallToggleButton.isSelected(); - for (String feature : notificationFeatures) { - JFXToggleButton featureToggleButton = featureToggleButtons.get(feature); + private void handleToggleOverallSwitch(List<String> features) { + boolean newState = notificationOverallToggleButton.isSelected(); + for (String feature : features) { + JFXToggleButton featureToggleButton = notificationFeatureToggleButtons.get(feature); handleToggleFeatureSwitch(featureToggleButton, feature, newState); } } @@ -143,16 +115,69 @@ private void handleToggleOverallSwitch() { private void handleToggleFeatureSwitch(JFXToggleButton featureToggleButton, String feature, Boolean isOn) { featureToggleButton.setSelected(isOn); haveSettingsChanged.setValue(true); - System.out.println("Settings changed: " + haveSettingsChanged); - setFeatureSetting(feature, isOn); + setNotificationFeatureSetting(feature, isOn); + } + + private void setUpGlobalListeners() { + // Any change listener + hasChangedListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { + if (settingsListener != null) { + settingsListener.handleSettingsChanged(newValue); + } + }; + haveSettingsChanged.addListener(hasChangedListener); + + // Overall change listener + notificationOverallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { + handleToggleOverallSwitch(notificationFeatures); + }; + notificationOverallToggleButton.selectedProperty().addListener(notificationOverallChangeListener); + } + + public void commitChanges(UserSettings newSettings) { + // Temporarily remove listeners to prevent unpredictable behavior + removeListeners(); + + this.userSettings = newSettings; + updateUI(); + + reinstallListeners(); + haveSettingsChanged.setValue(false); } public void cancelChanges(UserSettings originalUserSettings) { - overallToggleButton.selectedProperty().removeListener(overallChangeListener); - setData(originalUserSettings); + // Temporarily remove listeners to prevent unpredictable behavior + removeListeners(); + + this.userSettings = originalUserSettings; + updateUI(); + + reinstallListeners(); haveSettingsChanged.setValue(false); } + private void reinstallListeners() { + notificationOverallToggleButton.selectedProperty().addListener(notificationOverallChangeListener); + haveSettingsChanged.addListener(hasChangedListener); + } + + private void removeListeners() { + notificationOverallToggleButton.selectedProperty().removeListener(notificationOverallChangeListener); + haveSettingsChanged.removeListener(hasChangedListener); + } + + private void updateUI() { + boolean overallSetting = aggregateNotificationSettings(); + notificationOverallToggleButton.setSelected(overallSetting); + + for (Map.Entry<String, JFXToggleButton> entry : notificationFeatureToggleButtons.entrySet()) { + String feature = entry.getKey(); + JFXToggleButton toggleButton = entry.getValue(); + boolean featureSetting = getNotificationFeatureSetting(feature); + toggleButton.setSelected(featureSetting); + } + } + // Utils private boolean aggregateNotificationSettings() { return userSettings.getNotificationSettings().isClientOrdersOn() && @@ -161,7 +186,7 @@ private boolean aggregateNotificationSettings() { userSettings.getNotificationSettings().isWarehouseInventoryOn(); } - private boolean getFeatureSetting(String feature) { + private boolean getNotificationFeatureSetting(String feature) { return switch (feature) { case "Supplier Orders" -> userSettings.getNotificationSettings().isSupplierOrdersOn(); case "Client Orders" -> userSettings.getNotificationSettings().isClientOrdersOn(); @@ -171,7 +196,7 @@ private boolean getFeatureSetting(String feature) { }; } - private void setFeatureSetting(String feature, boolean isOn) { + private void setNotificationFeatureSetting(String feature, boolean isOn) { switch (feature) { case "Supplier Orders" -> userSettings.getNotificationSettings().setSupplierOrdersOn(isOn); case "Client Orders" -> userSettings.getNotificationSettings().setClientOrdersOn(isOn); diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java index 98ea8089..7965c396 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java @@ -211,7 +211,7 @@ private Result<UserSettings> handleSaveResponse(Result<UserSettings> result) { generalSettingsController.setData(userSettings); } if (notificationSettingsController != null) { - notificationSettingsController.setData(userSettings); + notificationSettingsController.commitChanges(userSettings); } fallbackManager.setLoading(false); @@ -229,12 +229,13 @@ private Result<UserSettings> handleSaveException(Throwable ex) { @FXML private void handleCancel() { // Reselect based on original settings + userSettings = TenantSettingsContext.getCurrentUserSettings().deepCopy(); if (generalSettingsController != null) { - generalSettingsController.cancelChanges(TenantSettingsContext.getCurrentUserSettings().deepCopy()); + generalSettingsController.cancelChanges(userSettings); } if (notificationSettingsController != null) { - notificationSettingsController.cancelChanges(TenantSettingsContext.getCurrentUserSettings().deepCopy()); + notificationSettingsController.cancelChanges(userSettings); } handleSettingsChanged(false); } From a9333ade84e995c659c83ca82232deb1d855b58d Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:42:38 +0300 Subject: [PATCH 05/10] Add Email Notification Settings --- .../NotificationSettingsController.java | 168 +++++++++++++----- .../settings/model/NotificationSettings.java | 8 +- .../desktop/shared/enums/Feature.java | 7 +- src/main/resources/css/entity-page.css | 2 +- .../chainoptim/desktop/core/main/AppView.fxml | 2 +- .../settings/NotificationSettingsView.fxml | 14 +- 6 files changed, 150 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java index 5247d2be..fa1cd027 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java @@ -1,6 +1,7 @@ package org.chainoptim.desktop.core.settings.controller; import org.chainoptim.desktop.core.settings.model.UserSettings; +import org.chainoptim.desktop.shared.enums.Feature; import org.chainoptim.desktop.shared.util.DataReceiver; import com.jfoenix.controls.JFXToggleButton; @@ -21,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; public class NotificationSettingsController implements DataReceiver<UserSettings> { @@ -33,15 +35,19 @@ public class NotificationSettingsController implements DataReceiver<UserSettings private SettingsListener settingsListener; private ChangeListener<Boolean> hasChangedListener; private ChangeListener<Boolean> notificationOverallChangeListener; + private ChangeListener<Boolean> emailOverallChangeListener; // Constants - private static final List<String> notificationFeatures = List.of("Supplier Orders", "Client Orders", "Factory Inventory", "Warehouse Inventory"); + private static final List<Feature> notificationFeatures = List.of(Feature.SUPPLIER_ORDER, Feature.CLIENT_ORDER, Feature.FACTORY_INVENTORY, Feature.WAREHOUSE_INVENTORY); + private static final List<Feature> emailFeatures = List.of(Feature.SUPPLIER_ORDER, Feature.CLIENT_ORDER, Feature.FACTORY_INVENTORY, Feature.WAREHOUSE_INVENTORY); // FXML @FXML private VBox contentVBox; private JFXToggleButton notificationOverallToggleButton = new JFXToggleButton(); - private final Map<String, JFXToggleButton> notificationFeatureToggleButtons = new HashMap<>(); + private final Map<Feature, JFXToggleButton> notificationFeatureToggleButtons = new HashMap<>(); + private JFXToggleButton emailOverallToggleButton = new JFXToggleButton(); + private final Map<Feature, JFXToggleButton> emailFeatureToggleButtons = new HashMap<>(); @Override public void setData(UserSettings userSettings) { @@ -53,18 +59,28 @@ private void initializeUI() { contentVBox.getChildren().clear(); contentVBox.setSpacing(12); - renderOverallHBox(); + renderOverallHBox("Notifications"); - for (String feature : notificationFeatures) { - renderFeatureHBox(feature); + for (Feature feature : notificationFeatures) { + renderFeatureHBox(feature, "Notifications"); + } + + Region region = new Region(); + region.setMinHeight(8); + contentVBox.getChildren().add(region); + + renderOverallHBox("Emails"); + + for (Feature feature : emailFeatures) { + renderFeatureHBox(feature, "Emails"); } setUpGlobalListeners(); } - private void renderOverallHBox() { + private void renderOverallHBox(String type) { HBox overallHBox = new HBox(); - Label overallLabel = new Label("Overall"); + Label overallLabel = new Label(type); overallLabel.getStyleClass().add("settings-section-label"); overallHBox.getChildren().add(overallLabel); @@ -72,19 +88,25 @@ private void renderOverallHBox() { overallHBox.getChildren().add(region); HBox.setHgrow(region, Priority.ALWAYS); - notificationOverallToggleButton = new JFXToggleButton(); - boolean overallSetting = aggregateNotificationSettings(); - notificationOverallToggleButton.setSelected(overallSetting); - styleToggleButton(notificationOverallToggleButton); - overallHBox.getChildren().add(notificationOverallToggleButton); + if (Objects.equals(type, "Notifications")) { + boolean overallSetting = aggregateNotificationSettings(); + notificationOverallToggleButton.setSelected(overallSetting); + styleToggleButton(notificationOverallToggleButton); + overallHBox.getChildren().add(notificationOverallToggleButton); + } else if ("Emails".equals(type)) { + boolean overallSetting = aggregateEmailSettings(); + emailOverallToggleButton.setSelected(overallSetting); + styleToggleButton(emailOverallToggleButton); + overallHBox.getChildren().add(emailOverallToggleButton); + } contentVBox.getChildren().add(overallHBox); } - private void renderFeatureHBox(String feature) { + private void renderFeatureHBox(Feature feature, String type) { HBox featureHBox = new HBox(); featureHBox.setAlignment(Pos.CENTER_LEFT); - Label featureLabel = new Label(feature); + Label featureLabel = new Label(feature.toString()); featureLabel.getStyleClass().add("settings-label"); featureHBox.getChildren().add(featureLabel); @@ -93,29 +115,49 @@ private void renderFeatureHBox(String feature) { HBox.setHgrow(region, Priority.ALWAYS); JFXToggleButton toggleButton = new JFXToggleButton(); - boolean featureSetting = getNotificationFeatureSetting(feature); - toggleButton.setSelected(featureSetting); styleToggleButton(toggleButton); - toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> - handleToggleFeatureSwitch(notificationFeatureToggleButtons.get(feature), feature, newValue)); - notificationFeatureToggleButtons.put(feature, toggleButton); + if (Objects.equals(type, "Notifications")) { + boolean featureSetting = getNotificationFeatureSetting(feature); + toggleButton.setSelected(featureSetting); + toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> + handleToggleFeatureSwitch(notificationFeatureToggleButtons.get(feature), feature, newValue, type)); + notificationFeatureToggleButtons.put(feature, toggleButton); + } else if ("Emails".equals(type)) { + boolean featureSetting = getEmailFeatureSetting(feature); + toggleButton.setSelected(featureSetting); + toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> + handleToggleFeatureSwitch(emailFeatureToggleButtons.get(feature), feature, newValue, type)); + emailFeatureToggleButtons.put(feature, toggleButton); + } featureHBox.getChildren().add(toggleButton); contentVBox.getChildren().add(featureHBox); } - private void handleToggleOverallSwitch(List<String> features) { - boolean newState = notificationOverallToggleButton.isSelected(); - for (String feature : features) { - JFXToggleButton featureToggleButton = notificationFeatureToggleButtons.get(feature); - handleToggleFeatureSwitch(featureToggleButton, feature, newState); + private void handleToggleOverallSwitch(String type) { + if (Objects.equals(type, "Notifications")) { + boolean newState = notificationOverallToggleButton.isSelected(); + for (Feature feature : notificationFeatures) { + JFXToggleButton featureToggleButton = notificationFeatureToggleButtons.get(feature); + handleToggleFeatureSwitch(featureToggleButton, feature, newState, type); + } + } else if ("Emails".equals(type)) { + boolean newState = emailOverallToggleButton.isSelected(); + for (Feature feature : emailFeatures) { + JFXToggleButton featureToggleButton = emailFeatureToggleButtons.get(feature); + handleToggleFeatureSwitch(featureToggleButton, feature, newState, type); + } } } - private void handleToggleFeatureSwitch(JFXToggleButton featureToggleButton, String feature, Boolean isOn) { + private void handleToggleFeatureSwitch(JFXToggleButton featureToggleButton, Feature feature, Boolean isOn, String type) { featureToggleButton.setSelected(isOn); haveSettingsChanged.setValue(true); - setNotificationFeatureSetting(feature, isOn); + if (Objects.equals(type, "Notifications")) { + setNotificationFeatureSetting(feature, isOn); + } else if ("Emails".equals(type)) { + setEmailFeatureSetting(feature, isOn); + } } private void setUpGlobalListeners() { @@ -129,9 +171,14 @@ private void setUpGlobalListeners() { // Overall change listener notificationOverallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - handleToggleOverallSwitch(notificationFeatures); + handleToggleOverallSwitch("Notifications"); }; notificationOverallToggleButton.selectedProperty().addListener(notificationOverallChangeListener); + + emailOverallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { + handleToggleOverallSwitch("Emails"); + }; + emailOverallToggleButton.selectedProperty().addListener(emailOverallChangeListener); } public void commitChanges(UserSettings newSettings) { @@ -158,11 +205,13 @@ public void cancelChanges(UserSettings originalUserSettings) { private void reinstallListeners() { notificationOverallToggleButton.selectedProperty().addListener(notificationOverallChangeListener); + emailOverallToggleButton.selectedProperty().addListener(emailOverallChangeListener); haveSettingsChanged.addListener(hasChangedListener); } private void removeListeners() { notificationOverallToggleButton.selectedProperty().removeListener(notificationOverallChangeListener); + emailOverallToggleButton.selectedProperty().removeListener(emailOverallChangeListener); haveSettingsChanged.removeListener(hasChangedListener); } @@ -170,38 +219,75 @@ private void updateUI() { boolean overallSetting = aggregateNotificationSettings(); notificationOverallToggleButton.setSelected(overallSetting); - for (Map.Entry<String, JFXToggleButton> entry : notificationFeatureToggleButtons.entrySet()) { - String feature = entry.getKey(); + for (Map.Entry<Feature, JFXToggleButton> entry : notificationFeatureToggleButtons.entrySet()) { + Feature feature = entry.getKey(); JFXToggleButton toggleButton = entry.getValue(); boolean featureSetting = getNotificationFeatureSetting(feature); toggleButton.setSelected(featureSetting); } + + boolean emailOverallSetting = aggregateEmailSettings(); + emailOverallToggleButton.setSelected(emailOverallSetting); + + for (Map.Entry<Feature, JFXToggleButton> entry : emailFeatureToggleButtons.entrySet()) { + Feature feature = entry.getKey(); + JFXToggleButton toggleButton = entry.getValue(); + boolean featureSetting = getEmailFeatureSetting(feature); + toggleButton.setSelected(featureSetting); + } } // Utils private boolean aggregateNotificationSettings() { - return userSettings.getNotificationSettings().isClientOrdersOn() && - userSettings.getNotificationSettings().isSupplierOrdersOn() && + return userSettings.getNotificationSettings().isSupplierOrdersOn() && + userSettings.getNotificationSettings().isClientOrdersOn() && userSettings.getNotificationSettings().isFactoryInventoryOn() && userSettings.getNotificationSettings().isWarehouseInventoryOn(); } - private boolean getNotificationFeatureSetting(String feature) { + private boolean aggregateEmailSettings() { + return userSettings.getNotificationSettings().isEmailSupplierOrdersOn() && + userSettings.getNotificationSettings().isEmailClientOrdersOn() && + userSettings.getNotificationSettings().isEmailFactoryInventoryOn() && + userSettings.getNotificationSettings().isEmailWarehouseInventoryOn(); + } + + private boolean getNotificationFeatureSetting(Feature feature) { return switch (feature) { - case "Supplier Orders" -> userSettings.getNotificationSettings().isSupplierOrdersOn(); - case "Client Orders" -> userSettings.getNotificationSettings().isClientOrdersOn(); - case "Factory Inventory" -> userSettings.getNotificationSettings().isFactoryInventoryOn(); - case "Warehouse Inventory" -> userSettings.getNotificationSettings().isWarehouseInventoryOn(); + case SUPPLIER_ORDER -> userSettings.getNotificationSettings().isSupplierOrdersOn(); + case CLIENT_ORDER -> userSettings.getNotificationSettings().isClientOrdersOn(); + case FACTORY_INVENTORY -> userSettings.getNotificationSettings().isFactoryInventoryOn(); + case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().isWarehouseInventoryOn(); default -> false; }; } - private void setNotificationFeatureSetting(String feature, boolean isOn) { + private boolean getEmailFeatureSetting(Feature feature) { + return switch (feature) { + case SUPPLIER_ORDER -> userSettings.getNotificationSettings().isEmailSupplierOrdersOn(); + case CLIENT_ORDER -> userSettings.getNotificationSettings().isEmailClientOrdersOn(); + case FACTORY_INVENTORY -> userSettings.getNotificationSettings().isEmailFactoryInventoryOn(); + case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().isEmailWarehouseInventoryOn(); + default -> false; + }; + } + + private void setNotificationFeatureSetting(Feature feature, boolean isOn) { + switch (feature) { + case SUPPLIER_ORDER -> userSettings.getNotificationSettings().setSupplierOrdersOn(isOn); + case CLIENT_ORDER -> userSettings.getNotificationSettings().setClientOrdersOn(isOn); + case FACTORY_INVENTORY -> userSettings.getNotificationSettings().setFactoryInventoryOn(isOn); + case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().setWarehouseInventoryOn(isOn); + default -> {} + } + } + + private void setEmailFeatureSetting(Feature feature, boolean isOn) { switch (feature) { - case "Supplier Orders" -> userSettings.getNotificationSettings().setSupplierOrdersOn(isOn); - case "Client Orders" -> userSettings.getNotificationSettings().setClientOrdersOn(isOn); - case "Factory Inventory" -> userSettings.getNotificationSettings().setFactoryInventoryOn(isOn); - case "Warehouse Inventory" -> userSettings.getNotificationSettings().setWarehouseInventoryOn(isOn); + case SUPPLIER_ORDER -> userSettings.getNotificationSettings().setEmailSupplierOrdersOn(isOn); + case CLIENT_ORDER -> userSettings.getNotificationSettings().setEmailClientOrdersOn(isOn); + case FACTORY_INVENTORY -> userSettings.getNotificationSettings().setEmailFactoryInventoryOn(isOn); + case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().setEmailWarehouseInventoryOn(isOn); default -> {} } } diff --git a/src/main/java/org/chainoptim/desktop/core/settings/model/NotificationSettings.java b/src/main/java/org/chainoptim/desktop/core/settings/model/NotificationSettings.java index bc1c0428..2c025838 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/model/NotificationSettings.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/model/NotificationSettings.java @@ -13,8 +13,14 @@ public class NotificationSettings { private boolean clientOrdersOn; private boolean factoryInventoryOn; private boolean warehouseInventoryOn; + private boolean emailSupplierOrdersOn; + private boolean emailClientOrdersOn; + private boolean emailFactoryInventoryOn; + private boolean emailWarehouseInventoryOn; public NotificationSettings deepCopy() { - return new NotificationSettings(supplierOrdersOn, clientOrdersOn, factoryInventoryOn, warehouseInventoryOn); + return new NotificationSettings( + supplierOrdersOn, clientOrdersOn, factoryInventoryOn, warehouseInventoryOn, + emailSupplierOrdersOn, emailClientOrdersOn, emailFactoryInventoryOn, emailWarehouseInventoryOn); } } diff --git a/src/main/java/org/chainoptim/desktop/shared/enums/Feature.java b/src/main/java/org/chainoptim/desktop/shared/enums/Feature.java index 7e7762e7..18bba397 100644 --- a/src/main/java/org/chainoptim/desktop/shared/enums/Feature.java +++ b/src/main/java/org/chainoptim/desktop/shared/enums/Feature.java @@ -31,5 +31,10 @@ public enum Feature { CLIENT, CLIENT_ORDER, CLIENT_SHIPMENT, - CLIENT_EVALUATION + CLIENT_EVALUATION; + + @Override + public String toString() { + return (name().charAt(0) + name().substring(1).toLowerCase()).replace("_", " "); + } } diff --git a/src/main/resources/css/entity-page.css b/src/main/resources/css/entity-page.css index b42b3bf8..e7bab219 100644 --- a/src/main/resources/css/entity-page.css +++ b/src/main/resources/css/entity-page.css @@ -1,7 +1,7 @@ .entity-header-container { -fx-padding: 20; -fx-background-color: #f2f2f2; - -fx-border-color: #c0c0c0 #ffffff #c0c0c0 #ffffff; + -fx-border-color: #ffffff #ffffff #c0c0c0 #ffffff; -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 4, 0, 0, 2); } 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 bf159ffa..ec0e3852 100644 --- a/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml @@ -4,7 +4,7 @@ <BorderPane xmlns = "http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.core.main.controller.AppController" - style="-fx-background-color: #ffffff;"> + style="-fx-background-color: #ffffff; -fx-border-width: 1px 0px; -fx-border-color: #c0c0c0;"> <!-- Sidebar --> <left> diff --git a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml index d8066a6e..be0910ca 100644 --- a/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/settings/NotificationSettingsView.fxml @@ -1,14 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> +<?import javafx.scene.control.ScrollPane?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - fx:controller="org.chainoptim.desktop.core.settings.controller.NotificationSettingsController" - prefHeight="400.0" prefWidth="600.0"> + fx:controller="org.chainoptim.desktop.core.settings.controller.NotificationSettingsController"> - <VBox fx:id="contentVBox" styleClass="settings-tab-container"> - <!-- Overall Settings --> + <ScrollPane fitToHeight="true" fitToWidth="true"> + <VBox fx:id="contentVBox" styleClass="settings-tab-container"> + <!-- Overall Settings --> - <!-- Feature-specific Settings --> - </VBox> + <!-- Feature-specific Settings --> + </VBox> + </ScrollPane> </VBox> From 5272ab3932cd8f7809519b34b7ea01cf373ec385 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:15:48 +0300 Subject: [PATCH 06/10] Make minor adjustments to User Settings --- .../NotificationSettingsController.java | 59 +++++++++++-------- .../controller/SettingsController.java | 9 ++- .../client/service/ClientServiceImpl.java | 1 - .../uielements/settings/EnumSelector.java | 13 ++-- src/main/resources/css/list-header.css | 20 +------ .../desktop/core/main/ListHeaderView.fxml | 2 +- 6 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java index fa1cd027..0f51ed6f 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/NotificationSettingsController.java @@ -1,5 +1,6 @@ package org.chainoptim.desktop.core.settings.controller; +import org.chainoptim.desktop.core.context.TenantContext; import org.chainoptim.desktop.core.settings.model.UserSettings; import org.chainoptim.desktop.shared.enums.Feature; import org.chainoptim.desktop.shared.util.DataReceiver; @@ -19,15 +20,14 @@ import javafx.scene.paint.Color; import lombok.Setter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; public class NotificationSettingsController implements DataReceiver<UserSettings> { // State private UserSettings userSettings; + private boolean areNotificationsWithinPlan; + private boolean areEmailsWithinPlan; private final BooleanProperty haveSettingsChanged = new SimpleBooleanProperty(false); // Listeners @@ -40,18 +40,23 @@ public class NotificationSettingsController implements DataReceiver<UserSettings // Constants private static final List<Feature> notificationFeatures = List.of(Feature.SUPPLIER_ORDER, Feature.CLIENT_ORDER, Feature.FACTORY_INVENTORY, Feature.WAREHOUSE_INVENTORY); private static final List<Feature> emailFeatures = List.of(Feature.SUPPLIER_ORDER, Feature.CLIENT_ORDER, Feature.FACTORY_INVENTORY, Feature.WAREHOUSE_INVENTORY); - + private static final String NOTIFICATIONS = "Notifications"; + private static final String EMAILS = "Emails"; + // FXML @FXML private VBox contentVBox; - private JFXToggleButton notificationOverallToggleButton = new JFXToggleButton(); - private final Map<Feature, JFXToggleButton> notificationFeatureToggleButtons = new HashMap<>(); - private JFXToggleButton emailOverallToggleButton = new JFXToggleButton(); - private final Map<Feature, JFXToggleButton> emailFeatureToggleButtons = new HashMap<>(); + private final JFXToggleButton notificationOverallToggleButton = new JFXToggleButton(); + private final Map<Feature, JFXToggleButton> notificationFeatureToggleButtons = new EnumMap<>(Feature.class); + private final JFXToggleButton emailOverallToggleButton = new JFXToggleButton(); + private final Map<Feature, JFXToggleButton> emailFeatureToggleButtons = new EnumMap<>(Feature.class); @Override public void setData(UserSettings userSettings) { this.userSettings = userSettings; + this.areNotificationsWithinPlan = TenantContext.getCurrentUser().getOrganization().getSubscriptionPlan().isCustomNotificationsOn(); + this.areEmailsWithinPlan = TenantContext.getCurrentUser().getOrganization().getSubscriptionPlan().isEmailNotificationsOn(); + initializeUI(); } @@ -59,20 +64,20 @@ private void initializeUI() { contentVBox.getChildren().clear(); contentVBox.setSpacing(12); - renderOverallHBox("Notifications"); + renderOverallHBox(NOTIFICATIONS); for (Feature feature : notificationFeatures) { - renderFeatureHBox(feature, "Notifications"); + renderFeatureHBox(feature, NOTIFICATIONS); } Region region = new Region(); region.setMinHeight(8); contentVBox.getChildren().add(region); - renderOverallHBox("Emails"); + renderOverallHBox(EMAILS); for (Feature feature : emailFeatures) { - renderFeatureHBox(feature, "Emails"); + renderFeatureHBox(feature, EMAILS); } setUpGlobalListeners(); @@ -88,14 +93,16 @@ private void renderOverallHBox(String type) { overallHBox.getChildren().add(region); HBox.setHgrow(region, Priority.ALWAYS); - if (Objects.equals(type, "Notifications")) { + if (Objects.equals(type, NOTIFICATIONS)) { boolean overallSetting = aggregateNotificationSettings(); notificationOverallToggleButton.setSelected(overallSetting); + notificationOverallToggleButton.setDisable(!areNotificationsWithinPlan); styleToggleButton(notificationOverallToggleButton); overallHBox.getChildren().add(notificationOverallToggleButton); - } else if ("Emails".equals(type)) { + } else if (EMAILS.equals(type)) { boolean overallSetting = aggregateEmailSettings(); emailOverallToggleButton.setSelected(overallSetting); + emailOverallToggleButton.setDisable(!areEmailsWithinPlan); styleToggleButton(emailOverallToggleButton); overallHBox.getChildren().add(emailOverallToggleButton); } @@ -116,14 +123,16 @@ private void renderFeatureHBox(Feature feature, String type) { JFXToggleButton toggleButton = new JFXToggleButton(); styleToggleButton(toggleButton); - if (Objects.equals(type, "Notifications")) { + if (Objects.equals(type, NOTIFICATIONS)) { boolean featureSetting = getNotificationFeatureSetting(feature); toggleButton.setSelected(featureSetting); + toggleButton.setDisable(!areNotificationsWithinPlan); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> handleToggleFeatureSwitch(notificationFeatureToggleButtons.get(feature), feature, newValue, type)); notificationFeatureToggleButtons.put(feature, toggleButton); - } else if ("Emails".equals(type)) { + } else if (EMAILS.equals(type)) { boolean featureSetting = getEmailFeatureSetting(feature); + toggleButton.setDisable(!areEmailsWithinPlan); toggleButton.setSelected(featureSetting); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> handleToggleFeatureSwitch(emailFeatureToggleButtons.get(feature), feature, newValue, type)); @@ -135,13 +144,13 @@ private void renderFeatureHBox(Feature feature, String type) { } private void handleToggleOverallSwitch(String type) { - if (Objects.equals(type, "Notifications")) { + if (Objects.equals(type, NOTIFICATIONS)) { boolean newState = notificationOverallToggleButton.isSelected(); for (Feature feature : notificationFeatures) { JFXToggleButton featureToggleButton = notificationFeatureToggleButtons.get(feature); handleToggleFeatureSwitch(featureToggleButton, feature, newState, type); } - } else if ("Emails".equals(type)) { + } else if (EMAILS.equals(type)) { boolean newState = emailOverallToggleButton.isSelected(); for (Feature feature : emailFeatures) { JFXToggleButton featureToggleButton = emailFeatureToggleButtons.get(feature); @@ -153,9 +162,9 @@ private void handleToggleOverallSwitch(String type) { private void handleToggleFeatureSwitch(JFXToggleButton featureToggleButton, Feature feature, Boolean isOn, String type) { featureToggleButton.setSelected(isOn); haveSettingsChanged.setValue(true); - if (Objects.equals(type, "Notifications")) { + if (Objects.equals(type, NOTIFICATIONS)) { setNotificationFeatureSetting(feature, isOn); - } else if ("Emails".equals(type)) { + } else if (EMAILS.equals(type)) { setEmailFeatureSetting(feature, isOn); } } @@ -171,12 +180,12 @@ private void setUpGlobalListeners() { // Overall change listener notificationOverallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - handleToggleOverallSwitch("Notifications"); + handleToggleOverallSwitch(NOTIFICATIONS); }; notificationOverallToggleButton.selectedProperty().addListener(notificationOverallChangeListener); emailOverallChangeListener = (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { - handleToggleOverallSwitch("Emails"); + handleToggleOverallSwitch(EMAILS); }; emailOverallToggleButton.selectedProperty().addListener(emailOverallChangeListener); } @@ -278,7 +287,7 @@ private void setNotificationFeatureSetting(Feature feature, boolean isOn) { case CLIENT_ORDER -> userSettings.getNotificationSettings().setClientOrdersOn(isOn); case FACTORY_INVENTORY -> userSettings.getNotificationSettings().setFactoryInventoryOn(isOn); case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().setWarehouseInventoryOn(isOn); - default -> {} + default -> throw new IllegalStateException("Unexpected value: " + feature); } } @@ -288,7 +297,7 @@ private void setEmailFeatureSetting(Feature feature, boolean isOn) { case CLIENT_ORDER -> userSettings.getNotificationSettings().setEmailClientOrdersOn(isOn); case FACTORY_INVENTORY -> userSettings.getNotificationSettings().setEmailFactoryInventoryOn(isOn); case WAREHOUSE_INVENTORY -> userSettings.getNotificationSettings().setEmailWarehouseInventoryOn(isOn); - default -> {} + default -> throw new IllegalStateException("Unexpected value: " + feature); } } diff --git a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java index 7965c396..7cd7a73d 100644 --- a/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java +++ b/src/main/java/org/chainoptim/desktop/core/settings/controller/SettingsController.java @@ -7,8 +7,11 @@ import org.chainoptim.desktop.core.settings.model.UserSettings; import org.chainoptim.desktop.core.settings.service.UserSettingsService; import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.DataReceiver; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; @@ -36,6 +39,7 @@ public class SettingsController implements Initializable, SettingsListener { private final UserSettingsService userSettingsService; private final CommonViewsLoader commonViewsLoader; private final ControllerFactory controllerFactory; + private final ToastManager toastManager; // Controllers private GeneralSettingsController generalSettingsController; @@ -69,10 +73,12 @@ public class SettingsController implements Initializable, SettingsListener { public SettingsController(UserSettingsService userSettingsService, CommonViewsLoader commonViewsLoader, ControllerFactory controllerFactory, + ToastManager toastManager, FallbackManager fallbackManager) { this.userSettingsService = userSettingsService; this.commonViewsLoader = commonViewsLoader; this.controllerFactory = controllerFactory; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -201,7 +207,7 @@ public void handleSave() { private Result<UserSettings> handleSaveResponse(Result<UserSettings> result) { Platform.runLater(() -> { if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to save user settings."); + toastManager.addToast(new ToastInfo("An error occurred", "Failed to save user settings.", OperationOutcome.ERROR)); return; } userSettings = result.getData(); @@ -214,6 +220,7 @@ private Result<UserSettings> handleSaveResponse(Result<UserSettings> result) { notificationSettingsController.commitChanges(userSettings); } + toastManager.addToast(new ToastInfo("Success", "User settings saved successfully.", OperationOutcome.SUCCESS)); fallbackManager.setLoading(false); handleSettingsChanged(false); }); diff --git a/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java b/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java index 8b8c4862..66c13eac 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/features/client/service/ClientServiceImpl.java @@ -53,7 +53,6 @@ public CompletableFuture<Result<PaginatedResults<Client>>> getClientsByOrganizat String cacheKey = CacheKeyBuilder.buildAdvancedSearchKey("clients", "organization", organizationId.toString(), searchParams); String routeAddress = rootAddress + cacheKey; - System.out.println("Token: " + tokenManager.getToken()); HttpRequest request = requestBuilder.buildReadRequest(routeAddress, tokenManager.getToken()); if (cachingService.isCached(cacheKey) && !cachingService.isStale(cacheKey)) { diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java index 671b0f7d..b7b10e80 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/settings/EnumSelector.java @@ -7,15 +7,13 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; public class EnumSelector<T extends Enum<T>> extends HBox { private final ObjectProperty<T> selectedValueProperty = new SimpleObjectProperty<>();; - private final Map<T, Button> buttons = new HashMap<>(); + private final Map<T, Button> buttons = new LinkedHashMap<>(); public EnumSelector() { super(); @@ -30,7 +28,6 @@ public void initializeSelector(Class<T> enumType, T selectedValue) { this.selectedValueProperty.setValue(selectedValue); int index = 0; - for (T value : enumType.getEnumConstants()) { // Create a button for each enum value Button button = new Button(value.toString()); @@ -60,14 +57,12 @@ private void styleButton(T selectedValue, T value, Button button, int index, Cla public void selectValue(T value, Class<T> enumType) { selectedValueProperty.setValue(value); + int index = 0; for (Map.Entry<T, Button> entry : buttons.entrySet()) { styleButton(value, entry.getKey(), entry.getValue(), index, enumType); index++; } - buttons.forEach((k, v) -> v.getStyleClass().add( - k.equals(selectedValueProperty.getValue()) ? "enum-select-item-selected" : "enum-select-item" - )); } public ObjectProperty<T> getValueProperty() { diff --git a/src/main/resources/css/list-header.css b/src/main/resources/css/list-header.css index 621a6a87..331c669f 100644 --- a/src/main/resources/css/list-header.css +++ b/src/main/resources/css/list-header.css @@ -2,6 +2,7 @@ -fx-background-color: #ffffff; -fx-spacing: 20px; -fx-border-color: #c0c0c0; + -fx-border-width: 0px 0px 1px 0px; -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.15), 5, 0, 0, 2); } @@ -40,25 +41,6 @@ -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; -} - -.header .combo-box .arrow { - -fx-background-color: #c0c0c0; -} - .ordering-button { -fx-pref-height: 32px; -fx-background-color: transparent; diff --git a/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml b/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml index fd24340d..929791ef 100644 --- a/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/main/ListHeaderView.fxml @@ -20,7 +20,7 @@ <Button fx:id="searchButton" onAction="#handleSearch" styleClass="search-button"/> </StackPane> <Region HBox.hgrow="ALWAYS"/> - <ComboBox fx:id="sortOptions" promptText="Sort By..." onAction="#handleSortOption"/> + <ComboBox fx:id="sortOptions" promptText="Sort By..." onAction="#handleSortOption" styleClass="custom-combo-box"/> <Button fx:id="orderingButton" onAction="#handleOrdering" styleClass="ordering-button"/> <Region HBox.hgrow="ALWAYS"/> <Button fx:id="refreshButton" styleClass="refresh-button"/> From 00fca6b466b4c6e704d31cebc5c52dbca56c1107 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:34:40 +0300 Subject: [PATCH 07/10] Start refactoring forms --- .../controller/CreateProductController.java | 86 ++++++++----- .../common/uielements/forms/FormField.java | 117 ++++++++++++++++++ .../uielements/forms/ValidationException.java | 8 ++ src/main/resources/css/common-elements.css | 2 +- src/main/resources/css/forms.css | 6 + .../features/product/CreateProductView.fxml | 9 +- 6 files changed, 192 insertions(+), 36 deletions(-) create mode 100644 src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java create mode 100644 src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/ValidationException.java 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 2ba86a8b..902a3267 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 @@ -7,27 +7,35 @@ import org.chainoptim.desktop.features.product.dto.CreateProductDTO; import org.chainoptim.desktop.features.product.model.Product; import org.chainoptim.desktop.features.product.service.ProductWriteService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateUnitOfMeasurementController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; import java.util.ResourceBundle; -public class CreateProductController implements Initializable { +public class CreateProductController { private final ProductWriteService productWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; private final FallbackManager fallbackManager; + private final ToastManager toastManager; private SelectOrCreateUnitOfMeasurementController unitOfMeasurementController; @@ -36,37 +44,42 @@ public class CreateProductController implements Initializable { @FXML private StackPane unitOfMeasurementContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @FXML - private TextField descriptionField; + private FormField<String> descriptionFormField; @Inject public CreateProductController( ProductWriteService productWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, - FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager ) { this.productWriteService = productWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } - @Override - public void initialize(URL location, ResourceBundle resources) { + public void initialize() { commonViewsLoader.loadFallbackManager(fallbackContainer); unitOfMeasurementController = commonViewsLoader.loadSelectOrCreateUnitOfMeasurement(unitOfMeasurementContainer); unitOfMeasurementController.initialize(); + + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, null, "Your input is not valid."); + descriptionFormField.initialize(String::new, "Description", false, null, "Your input is not valid."); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -74,33 +87,26 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateProductDTO productDTO = getCreateProductDTO(organizationId); - System.out.println("CreateProduct: " + productDTO.getUnitDTO()); + System.out.println("CreateProduct: " + productDTO); + if (productDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); productWriteService.createProduct(productDTO) - .thenAccept(result -> - // Navigate to product page - Platform.runLater(() -> { - if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create product."); - return; - } - Product product = result.getData(); - fallbackManager.setLoading(false); - currentSelectionService.setSelectedId(product.getId()); - navigationService.switchView("Product?id=" + product.getId(), true); - }) - ) - .exceptionally(ex -> { - ex.printStackTrace(); - return null; - }); + .thenApply(this::handleCreateProductResponse) + .exceptionally(this::handleCreateProductException); } private CreateProductDTO getCreateProductDTO(Integer organizationId) { CreateProductDTO productDTO = new CreateProductDTO(); productDTO.setOrganizationId(organizationId); - productDTO.setName(nameField.getText()); - productDTO.setDescription(descriptionField.getText()); + try { + productDTO.setName(nameFormField.handleSubmit()); + productDTO.setDescription(descriptionFormField.handleSubmit()); + } catch (ValidationException e) { + return null; + } if (unitOfMeasurementController.isCreatingNewUnit()) { productDTO.setCreateUnit(true); productDTO.setUnitDTO(unitOfMeasurementController.getNewUnitDTO()); @@ -111,4 +117,24 @@ private CreateProductDTO getCreateProductDTO(Integer organizationId) { return productDTO; } + + private Result<Product> handleCreateProductResponse(Result<Product> result) { + Platform.runLater(() -> { + if (result.getError() != null) { + fallbackManager.setErrorMessage("Failed to create product."); + return; + } + Product product = result.getData(); + fallbackManager.setLoading(false); + currentSelectionService.setSelectedId(product.getId()); + navigationService.switchView("Product?id=" + product.getId(), true); + }); + return result; + } + + private Result<Product> handleCreateProductException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast( + new ToastInfo("An error occurred.", "Failed to create product.", OperationOutcome.ERROR))); + return new Result<>(); + } } diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java new file mode 100644 index 00000000..d5437e81 --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java @@ -0,0 +1,117 @@ +package org.chainoptim.desktop.shared.common.uielements.forms; + +import org.chainoptim.desktop.shared.table.util.StringConverter; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +public class FormField<T> extends VBox { + + // Initial state + private StringConverter<T> stringConverter; + private String formLabel; + private boolean isMandatory; + private String errorMessage; + + // Current state + private String currentValue; + private boolean hasTriedSubmitting = false; + + // UI + private TextField textField; + private Label errorLabel; + private Label mandatoryErrorLabel; + + public void initialize(StringConverter<T> stringConverter, String formLabel, boolean isMandatory, T initialValue, String errorMessage) { + this.stringConverter = stringConverter; + this.formLabel = formLabel; + this.isMandatory = isMandatory; + this.errorMessage = errorMessage; + + this.currentValue = initialValue != null ? initialValue.toString() : ""; + + + initializeUI(); + } + + private void initializeUI() { + // Label + TextFlow label = new TextFlow(); + Text labelText = new Text(formLabel); + label.getChildren().add(labelText); + if (isMandatory) { + Text mandatoryText = new Text(" *"); + mandatoryText.setStyle("-fx-fill: #B22222;"); + label.getChildren().add(mandatoryText); + } + label.getStyleClass().add("form-label"); + this.getChildren().add(label); + + textField = new TextField(currentValue); + textField.getStyleClass().setAll("custom-text-field"); + textField.textProperty().addListener((observable, oldValue, newValue) -> { + currentValue = newValue; + if (hasTriedSubmitting) { + checkValidity(); + } + }); + this.getChildren().add(textField); + + VBox errorVBox = new VBox(4); + errorVBox.setStyle("-fx-padding: 4px 0px 0px 0px;"); + errorLabel = new Label(errorMessage); + errorLabel.getStyleClass().add("form-error-message"); + toggleNodeVisibility(errorLabel, false); + errorVBox.getChildren().add(errorLabel); + + mandatoryErrorLabel = new Label("This field is mandatory."); + mandatoryErrorLabel.getStyleClass().add("form-error-message"); + toggleNodeVisibility(mandatoryErrorLabel, false); + errorVBox.getChildren().add(mandatoryErrorLabel); + + this.getChildren().add(errorVBox); + } + + private void checkValidity() { + String input = textField.getText(); + if (isMandatory && input.isEmpty()) { + toggleNodeVisibility(mandatoryErrorLabel, true); + return; + } + try { + stringConverter.convert(input); + toggleNodeVisibility(errorLabel, false); + toggleNodeVisibility(mandatoryErrorLabel, false); + } catch (Exception e) { + toggleNodeVisibility(errorLabel, true); + } + } + + public T handleSubmit() throws ValidationException { + hasTriedSubmitting = true; + + String input = textField.getText(); + if (isMandatory && input.isEmpty()) { + toggleNodeVisibility(mandatoryErrorLabel, true); + throw new ValidationException("Mandatory field is empty."); + } + try { + T value = stringConverter.convert(input); + toggleNodeVisibility(errorLabel, false); + toggleNodeVisibility(mandatoryErrorLabel, false); + return value; + } catch (Exception e) { + toggleNodeVisibility(errorLabel, true); + throw new ValidationException("Mandatory field is empty."); + } + } + + private void toggleNodeVisibility(Node node, boolean isVisible) { + node.setVisible(isVisible); + node.setManaged(isVisible); + } +} diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/ValidationException.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/ValidationException.java new file mode 100644 index 00000000..fa98906d --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/ValidationException.java @@ -0,0 +1,8 @@ +package org.chainoptim.desktop.shared.common.uielements.forms; + +public class ValidationException extends Exception { + + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/resources/css/common-elements.css b/src/main/resources/css/common-elements.css index 4d9d59ed..19bb715b 100644 --- a/src/main/resources/css/common-elements.css +++ b/src/main/resources/css/common-elements.css @@ -45,7 +45,7 @@ .custom-text-field { -fx-min-height: 32px; -fx-max-height: 32px; - -fx-padding: 0px 4px; + -fx-padding: 0px 6px; -fx-background-color: #ffffff; -fx-font-weight: bold; -fx-border-color: #c0c0c0; diff --git a/src/main/resources/css/forms.css b/src/main/resources/css/forms.css index 51a4180c..0a2c51da 100644 --- a/src/main/resources/css/forms.css +++ b/src/main/resources/css/forms.css @@ -28,4 +28,10 @@ .radio-button { -fx-font-size: 14px; -fx-font-weight: bold; +} + +.form-error-message { + -fx-text-fill: #B22222; + -fx-font-weight: 600; + -fx-font-size: 13px; } \ No newline at end of file diff --git a/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml b/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml index 0943f16b..c970307a 100644 --- a/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> <?import javafx.scene.control.Label?> -<?import javafx.scene.control.TextField?> <?import javafx.scene.control.Button?> <?import javafx.scene.text.Text?> <?import javafx.scene.control.ScrollPane?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> + <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.product.controller.CreateProductController"> @@ -15,11 +16,9 @@ <Text text="Create Product" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> - <Label text="Description:" styleClass="form-label"/> - <TextField fx:id="descriptionField" styleClass="custom-text-field"/> + <FormField fx:id="descriptionFormField"/> <Label text="Unit of Measurement:" styleClass="form-label"/> <StackPane fx:id="unitOfMeasurementContainer"/> From 87516c8b4a2d128d3d6605f3f06f902b1b0200a2 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:30:50 +0300 Subject: [PATCH 08/10] Continue refactoring forms --- .../controller/CreateClientController.java | 74 +++++++++++++------ .../controller/UpdateClientController.java | 74 ++++++++++++------- .../controller/CreateProductController.java | 28 ++++--- .../controller/CreateComponentController.java | 69 +++++++---------- .../common/uielements/forms/FormField.java | 24 ++++-- .../SelectOrCreateLocationController.java | 61 ++++++++------- ...ctOrCreateUnitOfMeasurementController.java | 21 ++++-- .../SelectOrCreateLocationView.fxml | 23 +++--- .../SelectOrCreateUnitOfMeasurementView.fxml | 7 +- 9 files changed, 214 insertions(+), 167 deletions(-) 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 4c7e1d8f..56717f3f 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 @@ -7,9 +7,14 @@ import org.chainoptim.desktop.features.client.dto.CreateClientDTO; import org.chainoptim.desktop.features.client.model.Client; import org.chainoptim.desktop.features.client.service.ClientWriteService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import javafx.scene.layout.StackPane; @@ -17,7 +22,6 @@ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import java.net.URL; import java.util.ResourceBundle; @@ -28,6 +32,7 @@ public class CreateClientController implements Initializable { private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; private SelectOrCreateLocationController selectOrCreateLocationController; @@ -37,19 +42,21 @@ public class CreateClientController implements Initializable { @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject public CreateClientController(ClientWriteService clientWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, + ToastManager toastManager, FallbackManager fallbackManager, CommonViewsLoader commonViewsLoader) { this.clientWriteService = clientWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -57,14 +64,16 @@ public CreateClientController(ClientWriteService clientWriteService, public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); + + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, null, "Your input is not valid."); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -72,42 +81,59 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateClientDTO clientDTO = getCreateClientDTO(organizationId); + if (clientDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); clientWriteService.createClient(clientDTO) .thenApply(this::handleCreateClientResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleCreateClientException); + } + + private CreateClientDTO getCreateClientDTO(Integer organizationId) { + CreateClientDTO clientDTO = new CreateClientDTO(); + try { + clientDTO.setName(nameFormField.handleSubmit()); + clientDTO.setOrganizationId(organizationId); + if (selectOrCreateLocationController.isCreatingNewLocation()) { + clientDTO.setCreateLocation(true); + clientDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + clientDTO.setCreateLocation(false); + clientDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return clientDTO; } private Result<Client> handleCreateClientResponse(Result<Client> result) { Platform.runLater(() -> { if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create client."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to create client.", OperationOutcome.ERROR)); return; } Client client = result.getData(); fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Client created successfully.", OperationOutcome.SUCCESS)); + currentSelectionService.setSelectedId(client.getId()); navigationService.switchView("Client?id=" + client.getId(), true); }); return result; } - private CreateClientDTO getCreateClientDTO(Integer organizationId) { - CreateClientDTO clientDTO = new CreateClientDTO(); - clientDTO.setName(nameField.getText()); - clientDTO.setOrganizationId(organizationId); - if (selectOrCreateLocationController.isCreatingNewLocation()) { - clientDTO.setCreateLocation(true); - clientDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); - } else { - clientDTO.setCreateLocation(false); - clientDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); - } - - return clientDTO; + private Result<Client> handleCreateClientException(Throwable ex) { + Platform.runLater(() -> + toastManager.addToast(new ToastInfo( + "Error", "Failed to create client.", OperationOutcome.ERROR))); + return new Result<>(); } + } 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 index f7d0e298..681f24e7 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java @@ -7,9 +7,15 @@ 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.features.product.model.Product; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; @@ -30,6 +36,7 @@ public class UpdateClientController implements Initializable { private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; private Client client; @@ -41,7 +48,7 @@ public class UpdateClientController implements Initializable { @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject public UpdateClientController(ClientService clientService, @@ -49,12 +56,14 @@ public UpdateClientController(ClientService clientService, NavigationService navigationService, CurrentSelectionService currentSelectionService, CommonViewsLoader commonViewsLoader, + ToastManager toastManager, FallbackManager fallbackManager) { this.clientService = clientService; this.clientWriteService = clientWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -78,13 +87,15 @@ private void loadClient(Integer clientId) { private Result<Client> handleClientResponse(Result<Client> result) { Platform.runLater(() -> { if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to load client."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to update client.", OperationOutcome.ERROR)); return; } client = result.getData(); fallbackManager.setLoading(false); - nameField.setText(client.getName()); + initializeFormFields(); + selectOrCreateLocationController.setSelectedLocation(client.getLocation()); }); @@ -96,26 +107,49 @@ private Result<Client> handleClientException(Throwable ex) { return new Result<>(); } + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, client.getName(), "Your input is not valid"); + } + @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - UpdateClientDTO clientDTO = getUpdateClientDTO(); + if (clientDTO == null) return; System.out.println(clientDTO); + fallbackManager.reset(); + fallbackManager.setLoading(true); + clientWriteService.updateClient(clientDTO) .thenApply(this::handleUpdateClientResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleUpdateClientException); + } + + private UpdateClientDTO getUpdateClientDTO() { + UpdateClientDTO clientDTO = new UpdateClientDTO(); + clientDTO.setId(client.getId()); + try { + clientDTO.setName(nameFormField.handleSubmit()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + clientDTO.setCreateLocation(true); + clientDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + clientDTO.setCreateLocation(false); + clientDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return clientDTO; } private Result<Client> handleUpdateClientResponse(Result<Client> result) { Platform.runLater(() -> { if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create client."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to update client.", OperationOutcome.ERROR)); return; } fallbackManager.setLoading(false); @@ -130,20 +164,10 @@ private Result<Client> handleUpdateClientResponse(Result<Client> result) { return result; } - 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; + private Result<Client> handleUpdateClientException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to update client.", OperationOutcome.ERROR))); + return new Result<>(); } } 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 902a3267..fb5757d7 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 @@ -68,7 +68,6 @@ public CreateProductController( public void initialize() { commonViewsLoader.loadFallbackManager(fallbackContainer); unitOfMeasurementController = commonViewsLoader.loadSelectOrCreateUnitOfMeasurement(unitOfMeasurementContainer); - unitOfMeasurementController.initialize(); initializeFormFields(); } @@ -87,7 +86,6 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateProductDTO productDTO = getCreateProductDTO(organizationId); - System.out.println("CreateProduct: " + productDTO); if (productDTO == null) return; fallbackManager.reset(); @@ -104,16 +102,18 @@ private CreateProductDTO getCreateProductDTO(Integer organizationId) { try { productDTO.setName(nameFormField.handleSubmit()); productDTO.setDescription(descriptionFormField.handleSubmit()); + if (unitOfMeasurementController.isCreatingNewUnit()) { + productDTO.setCreateUnit(true); + productDTO.setUnitDTO(unitOfMeasurementController.getNewUnitDTO()); + } else { + productDTO.setCreateUnit(false); + if (unitOfMeasurementController.getSelectedUnit() != null) { + productDTO.setUnitId(unitOfMeasurementController.getSelectedUnit().getId()); + } + } } catch (ValidationException e) { return null; } - if (unitOfMeasurementController.isCreatingNewUnit()) { - productDTO.setCreateUnit(true); - productDTO.setUnitDTO(unitOfMeasurementController.getNewUnitDTO()); - } else { - productDTO.setCreateUnit(false); - productDTO.setUnitId(unitOfMeasurementController.getSelectedUnit().getId()); - } return productDTO; } @@ -121,11 +121,15 @@ private CreateProductDTO getCreateProductDTO(Integer organizationId) { private Result<Product> handleCreateProductResponse(Result<Product> result) { Platform.runLater(() -> { if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create product."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to create product.", OperationOutcome.ERROR)); return; } Product product = result.getData(); fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo + ("Product created.", "Product has been successfully created.", OperationOutcome.SUCCESS)); + currentSelectionService.setSelectedId(product.getId()); navigationService.switchView("Product?id=" + product.getId(), true); }); @@ -133,8 +137,8 @@ private Result<Product> handleCreateProductResponse(Result<Product> result) { } private Result<Product> handleCreateProductException(Throwable ex) { - Platform.runLater(() -> toastManager.addToast( - new ToastInfo("An error occurred.", "Failed to create product.", OperationOutcome.ERROR))); + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to create product.", OperationOutcome.ERROR))); return new Result<>(); } } 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 6c2aa86e..e727936b 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 @@ -8,8 +8,10 @@ import org.chainoptim.desktop.features.productpipeline.dto.CreateComponentDTO; import org.chainoptim.desktop.features.productpipeline.model.Component; import org.chainoptim.desktop.features.productpipeline.service.ComponentService; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateUnitOfMeasurementController; import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; import com.google.inject.Inject; @@ -30,8 +32,7 @@ public class CreateComponentController implements Initializable { private final ComponentService componentService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; - private final FXMLLoaderService fxmlLoaderService; - private final ControllerFactory controllerFactory; + private final CommonViewsLoader commonViewsLoader; private final FallbackManager fallbackManager; private SelectOrCreateUnitOfMeasurementController unitOfMeasurementController; @@ -51,52 +52,24 @@ public CreateComponentController( NavigationService navigationService, CurrentSelectionService currentSelectionService, FallbackManager fallbackManager, - FXMLLoaderService fxmlLoaderService, - ControllerFactory controllerFactory + CommonViewsLoader commonViewsLoader ) { this.componentService = componentService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; - this.fxmlLoaderService = fxmlLoaderService; - this.controllerFactory = controllerFactory; + this.commonViewsLoader = commonViewsLoader; this.fallbackManager = fallbackManager; } @Override public void initialize(URL location, ResourceBundle resources) { - loadFallbackManager(); - loadSelectOrCreateUnitOfMeasurement(); - } - - 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 loadSelectOrCreateUnitOfMeasurement() { - FXMLLoader loader = fxmlLoaderService.setUpLoader( - "/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateUnitOfMeasurementView.fxml", - controllerFactory::createController - ); - try { - Node selectOrCreateUnitOfMeasurementView = loader.load(); - unitOfMeasurementController = loader.getController(); - unitOfMeasurementContainer.getChildren().add(selectOrCreateUnitOfMeasurementView); - unitOfMeasurementController.initialize(); - } catch (IOException ex) { - ex.printStackTrace(); - } + commonViewsLoader.loadFallbackManager(fallbackContainer); + unitOfMeasurementController = commonViewsLoader.loadSelectOrCreateUnitOfMeasurement(unitOfMeasurementContainer); + unitOfMeasurementController.initialize(); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -104,7 +77,11 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateComponentDTO componentDTO = getCreateComponentDTO(organizationId); - System.out.println("CreateComponent: " + componentDTO.getUnitDTO()); + System.out.println("CreateComponent: " + componentDTO); + if (componentDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); componentService.createComponent(componentDTO) .thenAccept(result -> @@ -129,14 +106,18 @@ private void handleSubmit() { private CreateComponentDTO getCreateComponentDTO(Integer organizationId) { CreateComponentDTO componentDTO = new CreateComponentDTO(); componentDTO.setOrganizationId(organizationId); - componentDTO.setName(nameField.getText()); - componentDTO.setDescription(descriptionField.getText()); - if (unitOfMeasurementController.isCreatingNewUnit()) { - componentDTO.setCreateUnit(true); - componentDTO.setUnitDTO(unitOfMeasurementController.getNewUnitDTO()); - } else { - componentDTO.setCreateUnit(false); - componentDTO.setUnitId(unitOfMeasurementController.getSelectedUnit().getId()); + try { + componentDTO.setName(nameField.getText()); + componentDTO.setDescription(descriptionField.getText()); + if (unitOfMeasurementController.isCreatingNewUnit()) { + componentDTO.setCreateUnit(true); + componentDTO.setUnitDTO(unitOfMeasurementController.getNewUnitDTO()); + } else { + componentDTO.setCreateUnit(false); + componentDTO.setUnitId(unitOfMeasurementController.getSelectedUnit().getId()); + } + } catch (ValidationException e) { + return null; } return componentDTO; diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java index d5437e81..9c60188e 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/forms/FormField.java @@ -34,10 +34,14 @@ public void initialize(StringConverter<T> stringConverter, String formLabel, boo this.currentValue = initialValue != null ? initialValue.toString() : ""; - initializeUI(); } + public void setInitialValue(T initialValue) { + this.currentValue = initialValue != null ? initialValue.toString() : ""; + textField.setText(currentValue); + } + private void initializeUI() { // Label TextFlow label = new TextFlow(); @@ -78,9 +82,11 @@ private void initializeUI() { private void checkValidity() { String input = textField.getText(); - if (isMandatory && input.isEmpty()) { - toggleNodeVisibility(mandatoryErrorLabel, true); - return; + if (input.isEmpty()) { + if (isMandatory) { + toggleNodeVisibility(mandatoryErrorLabel, true); + } + return; // Skip parsing for non-mandatory fields } try { stringConverter.convert(input); @@ -95,10 +101,14 @@ public T handleSubmit() throws ValidationException { hasTriedSubmitting = true; String input = textField.getText(); - if (isMandatory && input.isEmpty()) { - toggleNodeVisibility(mandatoryErrorLabel, true); - throw new ValidationException("Mandatory field is empty."); + if (input.isEmpty()) { + if (isMandatory) { + toggleNodeVisibility(mandatoryErrorLabel, true); + throw new ValidationException("Mandatory field is empty."); + } + return null; // Skip parsing for non-mandatory fields } + try { T value = stringConverter.convert(input); toggleNodeVisibility(errorLabel, false); diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java index 0797eb96..5b3384ff 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateLocationController.java @@ -4,6 +4,8 @@ import javafx.scene.layout.VBox; import org.chainoptim.desktop.core.context.TenantContext; import org.chainoptim.desktop.core.user.model.User; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.features.location.dto.CreateLocationDTO; import org.chainoptim.desktop.shared.features.location.model.Location; import org.chainoptim.desktop.shared.features.location.service.LocationService; @@ -29,19 +31,19 @@ public class SelectOrCreateLocationController { @FXML private VBox createLocationForm; @FXML - private TextField addressField; + private FormField<String> addressField; @FXML - private TextField cityField; + private FormField<String> cityField; @FXML - private TextField stateField; + private FormField<String> stateField; @FXML - private TextField countryField; + private FormField<String> countryField; @FXML - private TextField zipCodeField; + private FormField<String> zipCodeField; @FXML - private TextField latitudeField; + private FormField<Double> latitudeField; @FXML - private TextField longitudeField; + private FormField<Double> longitudeField; private final ToggleGroup locationToggleGroup = new ToggleGroup(); @@ -62,9 +64,21 @@ public void initialize() { toggleVisibilityBasedOnSelection(); }); + initializeFormFields(); + loadLocations(); } + private void initializeFormFields() { + addressField.initialize(String::new, "Address", false, null, "Your input is not valid."); + cityField.initialize(String::new, "City", false, null, "Your input is not valid."); + stateField.initialize(String::new, "State", false, null, "Your input is not valid."); + countryField.initialize(String::new, "Country", false, null, "Your input is not valid."); + zipCodeField.initialize(String::new, "Zip Code", false, null, "Your input is not valid."); + latitudeField.initialize(Double::parseDouble, "Latitude", false, null, "Your input is not a number."); + longitudeField.initialize(Double::parseDouble, "Longitude", false, null, "Your input is not a number."); + } + private void loadLocations() { locationComboBox.setCellFactory(lv -> new ListCell<Location>() { @Override @@ -129,34 +143,17 @@ public void setSelectedLocation(Location location) { } - public CreateLocationDTO getNewLocationDTO() { + public CreateLocationDTO getNewLocationDTO() throws ValidationException { CreateLocationDTO locationDTO = new CreateLocationDTO(); locationDTO.setOrganizationId(organizationId); - locationDTO.setAddress(addressField.getText()); - locationDTO.setCity(cityField.getText()); - locationDTO.setState(stateField.getText()); - locationDTO.setCountry(countryField.getText()); - locationDTO.setZipCode(zipCodeField.getText()); - - String latitudeStr = latitudeField.getText().trim(); - if (!latitudeStr.isEmpty()) { - try { - double latitude = Double.parseDouble(latitudeStr); - locationDTO.setLatitude(latitude); - } catch (NumberFormatException e) { - System.out.println("Error parsing longitude"); - } - } + locationDTO.setAddress(addressField.handleSubmit()); + locationDTO.setCity(cityField.handleSubmit()); + locationDTO.setState(stateField.handleSubmit()); + locationDTO.setCountry(countryField.handleSubmit()); + locationDTO.setZipCode(zipCodeField.handleSubmit()); + locationDTO.setLatitude(latitudeField.handleSubmit()); + locationDTO.setLongitude(longitudeField.handleSubmit()); - String longitudeStr = longitudeField.getText().trim(); - if (!longitudeStr.isEmpty()) { - try { - double longitude = Double.parseDouble(longitudeStr); - locationDTO.setLongitude(longitude); - } catch (NumberFormatException e) { - System.out.println("Error parsing longitude"); - } - } return locationDTO; } diff --git a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateUnitOfMeasurementController.java b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateUnitOfMeasurementController.java index 874b7ff1..b1c42d30 100644 --- a/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateUnitOfMeasurementController.java +++ b/src/main/java/org/chainoptim/desktop/shared/common/uielements/select/SelectOrCreateUnitOfMeasurementController.java @@ -10,6 +10,8 @@ import org.chainoptim.desktop.features.product.dto.CreateUnitOfMeasurementDTO; import org.chainoptim.desktop.features.product.model.UnitOfMeasurement; import org.chainoptim.desktop.features.product.service.UnitOfMeasurementService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.httphandling.Result; import java.util.List; @@ -28,9 +30,9 @@ public class SelectOrCreateUnitOfMeasurementController { @FXML private VBox createUnitForm; @FXML - private TextField nameField; + private FormField<String> unitNameField; @FXML - private TextField unitTypeField; + private FormField<String> unitTypeField; private final ToggleGroup unitToggleGroup = new ToggleGroup(); @@ -51,6 +53,8 @@ public void initialize() { toggleVisibilityBasedOnSelection(); }); + initializeFormFields(); + loadUnitsOfMeasurement(); } @@ -101,6 +105,11 @@ private Result<List<UnitOfMeasurement>> handleUnitsException(Throwable ex) { return new Result<>(); } + private void initializeFormFields() { + unitNameField.initialize(String::new, "Name", true, null, "Your input is not valid."); + unitTypeField.initialize(String::new, "Type", true, null, "Your input is not valid."); + } + private void toggleVisibilityBasedOnSelection() { boolean isSelectExistingSelected = selectExistingRadio.isSelected(); unitComboBox.setVisible(isSelectExistingSelected); @@ -113,11 +122,13 @@ public UnitOfMeasurement getSelectedUnit() { return unitComboBox.getSelectionModel().getSelectedItem(); } - public CreateUnitOfMeasurementDTO getNewUnitDTO() { + public CreateUnitOfMeasurementDTO getNewUnitDTO() throws ValidationException { CreateUnitOfMeasurementDTO unitDTO = new CreateUnitOfMeasurementDTO(); unitDTO.setOrganizationId(organizationId); - unitDTO.setName(nameField.getText()); - unitDTO.setUnitType(unitTypeField.getText()); + + unitDTO.setName(unitNameField.handleSubmit()); + unitDTO.setUnitType(unitTypeField.handleSubmit()); + return unitDTO; } diff --git a/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml b/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml index 0739ce00..76be1e26 100644 --- a/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml +++ b/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateLocationView.fxml @@ -5,6 +5,8 @@ <?import javafx.scene.control.Label?> <?import javafx.scene.control.TextField?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> + <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController" spacing="16"> @@ -19,25 +21,18 @@ <!-- Create New Location subform --> <VBox fx:id="createLocationForm" visible="false"> - <Label text="Address" styleClass="form-label"/> - <TextField fx:id="addressField" styleClass="custom-text-field"/> + <FormField fx:id="addressField"/> - <Label text="City" styleClass="form-label"/> - <TextField fx:id="cityField" styleClass="custom-text-field"/> + <FormField fx:id="cityField"/> - <Label text="State" styleClass="form-label"/> - <TextField fx:id="stateField" styleClass="custom-text-field"/> + <FormField fx:id="stateField"/> - <Label text="Country" styleClass="form-label"/> - <TextField fx:id="countryField" styleClass="custom-text-field"/> + <FormField fx:id="countryField"/> - <Label text="Zip Code" styleClass="form-label"/> - <TextField fx:id="zipCodeField" styleClass="custom-text-field"/> + <FormField fx:id="zipCodeField"/> - <Label text="Latitude" styleClass="form-label"/> - <TextField fx:id="latitudeField" styleClass="custom-text-field"/> + <FormField fx:id="latitudeField"/> - <Label text="Longitude" styleClass="form-label"/> - <TextField fx:id="longitudeField" styleClass="custom-text-field"/> + <FormField fx:id="longitudeField"/> </VBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateUnitOfMeasurementView.fxml b/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateUnitOfMeasurementView.fxml index cb0cf8d0..07b787bb 100644 --- a/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateUnitOfMeasurementView.fxml +++ b/src/main/resources/org/chainoptim/desktop/shared/common/uielements/SelectOrCreateUnitOfMeasurementView.fxml @@ -5,6 +5,7 @@ <?import javafx.scene.control.Label?> <?import javafx.scene.control.TextField?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateUnitOfMeasurementController" spacing="16"> @@ -19,10 +20,8 @@ <!-- Create New Unit subform --> <VBox fx:id="createUnitForm" visible="false"> - <Label text="Name *" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="unitNameField"/> - <Label text="Unit Type" styleClass="form-label"/> - <TextField fx:id="unitTypeField" styleClass="custom-text-field"/> + <FormField fx:id="unitTypeField"/> </VBox> </VBox> From 3d8d669d3552f6bf94a85f0f0786e66e6c83802d Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:58:11 +0300 Subject: [PATCH 09/10] Finish refactoring forms for main features --- .../controller/CreateClientController.java | 19 ++-- .../controller/UpdateClientController.java | 32 ++++--- .../controller/CreateFactoryController.java | 69 ++++++++++---- .../controller/UpdateFactoryController.java | 94 ++++++++++++------- .../controller/CreateProductController.java | 2 +- .../controller/CreateSupplierController.java | 84 +++++++++++------ .../controller/UpdateSupplierController.java | 89 ++++++++++++------ .../controller/CreateWarehouseController.java | 86 +++++++++++------ .../controller/UpdateWarehouseController.java | 94 ++++++++++++------- .../features/client/CreateClientView.fxml | 4 +- .../features/client/UpdateClientView.fxml | 4 +- .../features/factory/CreateFactoryView.fxml | 6 +- .../features/factory/UpdateFactoryView.fxml | 4 +- .../features/supplier/CreateSupplierView.fxml | 6 +- .../features/supplier/UpdateSupplierView.fxml | 27 +++--- .../warehouse/CreateWarehouseView.fxml | 4 +- .../warehouse/UpdateWarehouseView.fxml | 4 +- 17 files changed, 398 insertions(+), 230 deletions(-) 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 56717f3f..d0fef093 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 @@ -28,6 +28,7 @@ public class CreateClientController implements Initializable { + // Services private final ClientWriteService clientWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; @@ -35,8 +36,10 @@ public class CreateClientController implements Initializable { private final ToastManager toastManager; private final FallbackManager fallbackManager; + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // FXML @FXML private StackPane fallbackContainer; @FXML @@ -44,14 +47,14 @@ public class CreateClientController implements Initializable { @FXML private FormField<String> nameFormField; - @Inject - public CreateClientController(ClientWriteService clientWriteService, - NavigationService navigationService, - CurrentSelectionService currentSelectionService, - ToastManager toastManager, - FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader) { + public CreateClientController( + ClientWriteService clientWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + ToastManager toastManager, + FallbackManager fallbackManager, + CommonViewsLoader commonViewsLoader) { this.clientWriteService = clientWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; @@ -112,13 +115,13 @@ private CreateClientDTO getCreateClientDTO(Integer organizationId) { private Result<Client> handleCreateClientResponse(Result<Client> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { toastManager.addToast(new ToastInfo( "Error", "Failed to create client.", OperationOutcome.ERROR)); return; } Client client = result.getData(); - fallbackManager.setLoading(false); toastManager.addToast(new ToastInfo( "Success", "Client created successfully.", OperationOutcome.SUCCESS)); 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 index 681f24e7..1091b325 100644 --- a/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java +++ b/src/main/java/org/chainoptim/desktop/features/client/controller/UpdateClientController.java @@ -31,6 +31,7 @@ public class UpdateClientController implements Initializable { + // Services private final ClientService clientService; private final ClientWriteService clientWriteService; private final NavigationService navigationService; @@ -39,10 +40,13 @@ public class UpdateClientController implements Initializable { private final ToastManager toastManager; private final FallbackManager fallbackManager; - private Client client; - + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // State + private Client client; + + // FXML @FXML private StackPane fallbackContainer; @FXML @@ -51,13 +55,14 @@ public class UpdateClientController implements Initializable { private FormField<String> nameFormField; @Inject - public UpdateClientController(ClientService clientService, - ClientWriteService clientWriteService, - NavigationService navigationService, - CurrentSelectionService currentSelectionService, - CommonViewsLoader commonViewsLoader, - ToastManager toastManager, - FallbackManager fallbackManager) { + public UpdateClientController( + ClientService clientService, + ClientWriteService clientWriteService, + NavigationService navigationService, + CurrentSelectionService currentSelectionService, + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager) { this.clientService = clientService; this.clientWriteService = clientWriteService; this.navigationService = navigationService; @@ -71,7 +76,6 @@ public UpdateClientController(ClientService clientService, public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); loadClient(currentSelectionService.getSelectedId()); } @@ -87,15 +91,13 @@ private void loadClient(Integer clientId) { private Result<Client> handleClientResponse(Result<Client> result) { Platform.runLater(() -> { if (result.getError() != null) { - toastManager.addToast(new ToastInfo( - "Error", "Failed to update client.", OperationOutcome.ERROR)); + fallbackManager.setErrorMessage("Failed to load client."); return; } client = result.getData(); fallbackManager.setLoading(false); initializeFormFields(); - selectOrCreateLocationController.setSelectedLocation(client.getLocation()); }); @@ -147,12 +149,14 @@ private UpdateClientDTO getUpdateClientDTO() { private Result<Client> handleUpdateClientResponse(Result<Client> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { toastManager.addToast(new ToastInfo( "Error", "Failed to update client.", OperationOutcome.ERROR)); return; } - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Client updated successfully.", OperationOutcome.SUCCESS)); // Manage navigation, invalidating previous client cache Client updatedClient = result.getData(); 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 952986f2..00d46bf3 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 @@ -7,16 +7,20 @@ import org.chainoptim.desktop.features.factory.dto.CreateFactoryDTO; import org.chainoptim.desktop.features.factory.model.Factory; import org.chainoptim.desktop.features.factory.service.FactoryWriteService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; @@ -24,20 +28,24 @@ public class CreateFactoryController implements Initializable { + // Services private final FactoryWriteService factoryWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject @@ -46,12 +54,14 @@ public CreateFactoryController( NavigationService navigationService, CurrentSelectionService currentSelectionService, CommonViewsLoader commonViewsLoader, + ToastManager toastManager, FallbackManager fallbackManager ) { this.factoryWriteService = factoryWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -59,14 +69,16 @@ public CreateFactoryController( public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); + + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, null, "Your input is not valid"); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -74,23 +86,29 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateFactoryDTO factoryDTO = getCreateFactoryDTO(organizationId); + if (factoryDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); factoryWriteService.createFactory(factoryDTO) .thenApply(this::handleCreateFactoryResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleCreateClientException); } private Result<Factory> handleCreateFactoryResponse(Result<Factory> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); + if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create factory."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to create factory.", OperationOutcome.ERROR)); return; } Factory factory = result.getData(); - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Factory created successfully.", OperationOutcome.SUCCESS)); + currentSelectionService.setSelectedId(factory.getId()); navigationService.switchView("Factory?id=" + factory.getId(), true); }); @@ -99,17 +117,28 @@ private Result<Factory> handleCreateFactoryResponse(Result<Factory> result) { private CreateFactoryDTO getCreateFactoryDTO(Integer organizationId) { CreateFactoryDTO factoryDTO = new CreateFactoryDTO(); - factoryDTO.setName(nameField.getText()); - factoryDTO.setOrganizationId(organizationId); - if (selectOrCreateLocationController.isCreatingNewLocation()) { - factoryDTO.setCreateLocation(true); - factoryDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); - } else { - factoryDTO.setCreateLocation(false); - factoryDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + try { + factoryDTO.setName(nameFormField.handleSubmit()); + factoryDTO.setOrganizationId(organizationId); + if (selectOrCreateLocationController.isCreatingNewLocation()) { + factoryDTO.setCreateLocation(true); + factoryDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + factoryDTO.setCreateLocation(false); + factoryDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; } return factoryDTO; } + + private Result<Factory> handleCreateClientException(Throwable ex) { + Platform.runLater(() -> + toastManager.addToast(new ToastInfo( + "Error", "Failed to create factory.", OperationOutcome.ERROR))); + return new Result<>(); + } } 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 index 50936286..1f6a32af 100644 --- a/src/main/java/org/chainoptim/desktop/features/factory/controller/UpdateFactoryController.java +++ b/src/main/java/org/chainoptim/desktop/features/factory/controller/UpdateFactoryController.java @@ -7,41 +7,49 @@ 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.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.fxml.Initializable; import javafx.application.Platform; import javafx.fxml.FXML; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; -import java.util.Optional; import java.util.ResourceBundle; public class UpdateFactoryController implements Initializable { + // Services private final FactoryService factoryService; private final FactoryWriteService factoryWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; - private Factory factory; - + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // State + private Factory factory; + + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject public UpdateFactoryController( @@ -49,13 +57,15 @@ public UpdateFactoryController( FactoryWriteService factoryWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, - FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager ) { this.factoryService = factoryService; this.factoryWriteService = factoryWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; + this.toastManager = toastManager; this.commonViewsLoader = commonViewsLoader; this.fallbackManager = fallbackManager; } @@ -63,8 +73,7 @@ public UpdateFactoryController( @FXML public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); - selectOrCreateLocationController =commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); + selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); loadFactory(currentSelectionService.getSelectedId()); } @@ -74,8 +83,7 @@ private void loadFactory(Integer factoryId) { factoryService.getFactoryById(factoryId) .thenApply(this::handleFactoryResponse) - .exceptionally(this::handleFactoryException) - .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + .exceptionally(this::handleFactoryException); } private Result<Factory> handleFactoryResponse(Result<Factory> result) { @@ -85,8 +93,9 @@ private Result<Factory> handleFactoryResponse(Result<Factory> result) { return; } factory = result.getData(); + fallbackManager.setLoading(false); - nameField.setText(factory.getName()); + initializeFormFields(); selectOrCreateLocationController.setSelectedLocation(factory.getLocation()); }); return result; @@ -97,29 +106,54 @@ private Result<Factory> handleFactoryException(Throwable ex) { return new Result<>(); } + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, factory.getName(), "Your input is not valid"); + } + @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - UpdateFactoryDTO factoryDTO = getUpdateFactoryDTO(); + if (factoryDTO == null) return; System.out.println(factoryDTO); + fallbackManager.reset(); + fallbackManager.setLoading(true); + factoryWriteService.updateFactory(factoryDTO) .thenApply(this::handleUpdateFactoryResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleUpdateFactoryException); + } + + private UpdateFactoryDTO getUpdateFactoryDTO() { + UpdateFactoryDTO factoryDTO = new UpdateFactoryDTO(); + try { + factoryDTO.setId(factory.getId()); + factoryDTO.setName(nameFormField.handleSubmit()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + factoryDTO.setCreateLocation(true); + factoryDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + factoryDTO.setCreateLocation(false); + factoryDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return factoryDTO; } private Result<Factory> handleUpdateFactoryResponse(Result<Factory> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create factory."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to update factory.", OperationOutcome.ERROR)); return; } - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Factory updated successfully.", OperationOutcome.SUCCESS)); // Manage navigation, invalidating previous factory cache Factory updatedFactory = result.getData(); @@ -131,20 +165,10 @@ private Result<Factory> handleUpdateFactoryResponse(Result<Factory> result) { return result; } - 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; + private Result<Factory> handleUpdateFactoryException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to update factory.", OperationOutcome.ERROR))); + return new Result<>(); } } 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 fb5757d7..e8e52079 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 @@ -34,8 +34,8 @@ public class CreateProductController { private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; - private final FallbackManager fallbackManager; private final ToastManager toastManager; + private final FallbackManager fallbackManager; private SelectOrCreateUnitOfMeasurementController unitOfMeasurementController; 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 269c48ca..ccc15b73 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 @@ -7,16 +7,20 @@ import org.chainoptim.desktop.features.supplier.dto.CreateSupplierDTO; import org.chainoptim.desktop.features.supplier.model.Supplier; import org.chainoptim.desktop.features.supplier.service.SupplierWriteService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; @@ -24,33 +28,38 @@ public class CreateSupplierController implements Initializable { + // Services private final SupplierWriteService supplierWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; - + private FormField<String> nameFormField; + @Inject public CreateSupplierController( SupplierWriteService supplierWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, + ToastManager toastManager, FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader - ) { + CommonViewsLoader commonViewsLoader) { this.supplierWriteService = supplierWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -58,14 +67,16 @@ public CreateSupplierController( public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); + + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, null, "Your input is not valid."); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -73,42 +84,59 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateSupplierDTO supplierDTO = getCreateSupplierDTO(organizationId); + if (supplierDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); supplierWriteService.createSupplier(supplierDTO) .thenApply(this::handleCreateSupplierResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleCreateSupplierException); + } + + private CreateSupplierDTO getCreateSupplierDTO(Integer organizationId) { + CreateSupplierDTO supplierDTO = new CreateSupplierDTO(); + try { + supplierDTO.setName(nameFormField.handleSubmit()); + supplierDTO.setOrganizationId(organizationId); + if (selectOrCreateLocationController.isCreatingNewLocation()) { + supplierDTO.setCreateLocation(true); + supplierDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + supplierDTO.setCreateLocation(false); + supplierDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return supplierDTO; } private Result<Supplier> handleCreateSupplierResponse(Result<Supplier> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create supplier."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to create supplier.", OperationOutcome.ERROR)); return; } Supplier supplier = result.getData(); - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Supplier created successfully.", OperationOutcome.SUCCESS)); + currentSelectionService.setSelectedId(supplier.getId()); navigationService.switchView("Supplier?id=" + supplier.getId(), true); }); return result; } - private CreateSupplierDTO getCreateSupplierDTO(Integer organizationId) { - CreateSupplierDTO supplierDTO = new CreateSupplierDTO(); - supplierDTO.setOrganizationId(organizationId); - 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; + private Result<Supplier> handleCreateSupplierException(Throwable ex) { + Platform.runLater(() -> + toastManager.addToast(new ToastInfo( + "Error", "Failed to create supplier.", OperationOutcome.ERROR))); + return new Result<>(); } + } 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 index 6923c9bf..df99247b 100644 --- a/src/main/java/org/chainoptim/desktop/features/supplier/controller/UpdateSupplierController.java +++ b/src/main/java/org/chainoptim/desktop/features/supplier/controller/UpdateSupplierController.java @@ -7,39 +7,49 @@ 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.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; + import java.net.URL; import java.util.ResourceBundle; public class UpdateSupplierController implements Initializable { + // Services private final SupplierService supplierService; private final SupplierWriteService supplierWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; - private Supplier supplier; - + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // State + private Supplier supplier; + + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject public UpdateSupplierController( @@ -47,14 +57,15 @@ public UpdateSupplierController( SupplierWriteService supplierWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, - FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader - ) { + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager) { this.supplierService = supplierService; this.supplierWriteService = supplierWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -62,7 +73,6 @@ public UpdateSupplierController( public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); loadSupplier(currentSelectionService.getSelectedId()); } @@ -84,9 +94,10 @@ private Result<Supplier> handleSupplierResponse(Result<Supplier> result) { supplier = result.getData(); fallbackManager.setLoading(false); - nameField.setText(supplier.getName()); + initializeFormFields(); selectOrCreateLocationController.setSelectedLocation(supplier.getLocation()); }); + return result; } @@ -95,28 +106,54 @@ private Result<Supplier> handleSupplierException(Throwable ex) { return new Result<>(); } + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, supplier.getName(), "Your input is not valid"); + } + @FXML private void handleSubmit() { + UpdateSupplierDTO supplierDTO = getUpdateSupplierDTO(); + if (supplierDTO == null) return; + System.out.println(supplierDTO); + fallbackManager.reset(); fallbackManager.setLoading(true); - UpdateSupplierDTO supplierDTO = getUpdateSupplierDTO(); - supplierWriteService.updateSupplier(supplierDTO) .thenApply(this::handleUpdateSupplierResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleUpdateSupplierException); + } + + private UpdateSupplierDTO getUpdateSupplierDTO() { + UpdateSupplierDTO supplierDTO = new UpdateSupplierDTO(); + supplierDTO.setId(supplier.getId()); + try { + supplierDTO.setName(nameFormField.handleSubmit()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + supplierDTO.setCreateLocation(true); + supplierDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + supplierDTO.setCreateLocation(false); + supplierDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return supplierDTO; } private Result<Supplier> handleUpdateSupplierResponse(Result<Supplier> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create supplier."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to update supplier.", OperationOutcome.ERROR)); return; } - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Supplier updated successfully.", OperationOutcome.SUCCESS)); // Manage navigation, invalidating previous supplier cache Supplier updatedSupplier = result.getData(); @@ -128,20 +165,10 @@ private Result<Supplier> handleUpdateSupplierResponse(Result<Supplier> result) { return result; } - 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; + private Result<Supplier> handleUpdateSupplierException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to update supplier.", OperationOutcome.ERROR))); + return new Result<>(); } } 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 07a1170e..25eddca6 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 @@ -7,16 +7,20 @@ import org.chainoptim.desktop.features.warehouse.dto.CreateWarehouseDTO; import org.chainoptim.desktop.features.warehouse.model.Warehouse; import org.chainoptim.desktop.features.warehouse.service.WarehouseWriteService; +import org.chainoptim.desktop.shared.common.uielements.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; @@ -24,35 +28,38 @@ public class CreateWarehouseController implements Initializable { + // Services private final WarehouseWriteService warehouseWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; - @FXML - private TextField locationIdField; + private FormField<String> nameFormField; @Inject public CreateWarehouseController( WarehouseWriteService warehouseWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, + ToastManager toastManager, FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader - ) { + CommonViewsLoader commonViewsLoader) { this.warehouseWriteService = warehouseWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -60,14 +67,16 @@ public CreateWarehouseController( public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); + + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, null, "Your input is not valid."); } @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { return; @@ -75,42 +84,59 @@ private void handleSubmit() { Integer organizationId = currentUser.getOrganization().getId(); CreateWarehouseDTO warehouseDTO = getCreateWarehouseDTO(organizationId); - System.out.println(warehouseDTO); + if (warehouseDTO == null) return; + + fallbackManager.reset(); + fallbackManager.setLoading(true); warehouseWriteService.createWarehouse(warehouseDTO) .thenApply(this::handleCreateWarehouseResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleCreateWarehouseException); + } + + private CreateWarehouseDTO getCreateWarehouseDTO(Integer organizationId) { + CreateWarehouseDTO warehouseDTO = new CreateWarehouseDTO(); + try { + warehouseDTO.setName(nameFormField.handleSubmit()); + warehouseDTO.setOrganizationId(organizationId); + if (selectOrCreateLocationController.isCreatingNewLocation()) { + warehouseDTO.setCreateLocation(true); + warehouseDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + warehouseDTO.setCreateLocation(false); + warehouseDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return warehouseDTO; } private Result<Warehouse> handleCreateWarehouseResponse(Result<Warehouse> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create new warehouse."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to create warehouse.", OperationOutcome.ERROR)); return; } Warehouse warehouse = result.getData(); - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Warehouse created successfully.", OperationOutcome.SUCCESS)); + currentSelectionService.setSelectedId(warehouse.getId()); navigationService.switchView("Warehouse?id=" + warehouse.getId(), true); }); return result; } - private CreateWarehouseDTO getCreateWarehouseDTO(Integer organizationId) { - CreateWarehouseDTO warehouseDTO = new CreateWarehouseDTO(); - warehouseDTO.setName(nameField.getText()); - warehouseDTO.setOrganizationId(organizationId); - if (selectOrCreateLocationController.isCreatingNewLocation()) { - warehouseDTO.setCreateLocation(true); - warehouseDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); - } else { - warehouseDTO.setCreateLocation(false); - warehouseDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); - } - - return warehouseDTO; + private Result<Warehouse> handleCreateWarehouseException(Throwable ex) { + Platform.runLater(() -> + toastManager.addToast(new ToastInfo( + "Error", "Failed to create warehouse.", OperationOutcome.ERROR))); + return new Result<>(); } + } + 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 index 03dff155..77273548 100644 --- a/src/main/java/org/chainoptim/desktop/features/warehouse/controller/UpdateWarehouseController.java +++ b/src/main/java/org/chainoptim/desktop/features/warehouse/controller/UpdateWarehouseController.java @@ -7,41 +7,49 @@ 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.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import com.google.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import java.net.URL; -import java.util.Optional; import java.util.ResourceBundle; public class UpdateWarehouseController implements Initializable { + // Services private final WarehouseService warehouseService; private final WarehouseWriteService warehouseWriteService; private final NavigationService navigationService; private final CurrentSelectionService currentSelectionService; private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; private final FallbackManager fallbackManager; - private Warehouse warehouse; - + // Controllers private SelectOrCreateLocationController selectOrCreateLocationController; + // State + private Warehouse warehouse; + + // FXML @FXML private StackPane fallbackContainer; @FXML private StackPane selectOrCreateLocationContainer; @FXML - private TextField nameField; + private FormField<String> nameFormField; @Inject public UpdateWarehouseController( @@ -49,14 +57,15 @@ public UpdateWarehouseController( WarehouseWriteService warehouseWriteService, NavigationService navigationService, CurrentSelectionService currentSelectionService, - FallbackManager fallbackManager, - CommonViewsLoader commonViewsLoader - ) { + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager) { this.warehouseService = warehouseService; this.warehouseWriteService = warehouseWriteService; this.navigationService = navigationService; this.currentSelectionService = currentSelectionService; this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; this.fallbackManager = fallbackManager; } @@ -64,7 +73,6 @@ public UpdateWarehouseController( public void initialize(URL location, ResourceBundle resources) { commonViewsLoader.loadFallbackManager(fallbackContainer); selectOrCreateLocationController = commonViewsLoader.loadSelectOrCreateLocation(selectOrCreateLocationContainer); - selectOrCreateLocationController.initialize(); loadWarehouse(currentSelectionService.getSelectedId()); } @@ -74,8 +82,7 @@ private void loadWarehouse(Integer warehouseId) { warehouseService.getWarehouseById(warehouseId) .thenApply(this::handleWarehouseResponse) - .exceptionally(this::handleWarehouseException) - .thenRun(() -> Platform.runLater(() -> fallbackManager.setLoading(false))); + .exceptionally(this::handleWarehouseException); } private Result<Warehouse> handleWarehouseResponse(Result<Warehouse> result) { @@ -85,10 +92,12 @@ private Result<Warehouse> handleWarehouseResponse(Result<Warehouse> result) { return; } warehouse = result.getData(); + fallbackManager.setLoading(false); - nameField.setText(warehouse.getName()); + initializeFormFields(); selectOrCreateLocationController.setSelectedLocation(warehouse.getLocation()); }); + return result; } @@ -97,29 +106,54 @@ private Result<Warehouse> handleWarehouseException(Throwable ex) { return new Result<>(); } + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, warehouse.getName(), "Your input is not valid"); + } + @FXML private void handleSubmit() { - fallbackManager.reset(); - fallbackManager.setLoading(true); - UpdateWarehouseDTO warehouseDTO = getUpdateWarehouseDTO(); + if (warehouseDTO == null) return; System.out.println(warehouseDTO); + fallbackManager.reset(); + fallbackManager.setLoading(true); + warehouseWriteService.updateWarehouse(warehouseDTO) .thenApply(this::handleUpdateWarehouseResponse) - .exceptionally(ex -> { - ex.printStackTrace(); - return new Result<>(); - }); + .exceptionally(this::handleUpdateWarehouseException); + } + + private UpdateWarehouseDTO getUpdateWarehouseDTO() { + UpdateWarehouseDTO warehouseDTO = new UpdateWarehouseDTO(); + warehouseDTO.setId(warehouse.getId()); + try { + warehouseDTO.setName(nameFormField.handleSubmit()); + + if (selectOrCreateLocationController.isCreatingNewLocation()) { + warehouseDTO.setCreateLocation(true); + warehouseDTO.setLocation(selectOrCreateLocationController.getNewLocationDTO()); + } else { + warehouseDTO.setCreateLocation(false); + warehouseDTO.setLocationId(selectOrCreateLocationController.getSelectedLocation().getId()); + } + } catch (ValidationException e) { + return null; + } + + return warehouseDTO; } private Result<Warehouse> handleUpdateWarehouseResponse(Result<Warehouse> result) { Platform.runLater(() -> { + fallbackManager.setLoading(false); if (result.getError() != null) { - fallbackManager.setErrorMessage("Failed to create warehouse."); + toastManager.addToast(new ToastInfo( + "Error", "Failed to update warehouse.", OperationOutcome.ERROR)); return; } - fallbackManager.setLoading(false); + toastManager.addToast(new ToastInfo( + "Success", "Warehouse updated successfully.", OperationOutcome.SUCCESS)); // Manage navigation, invalidating previous warehouse cache Warehouse updatedWarehouse = result.getData(); @@ -131,20 +165,10 @@ private Result<Warehouse> handleUpdateWarehouseResponse(Result<Warehouse> result return result; } - 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; + private Result<Warehouse> handleUpdateWarehouseException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to update warehouse.", OperationOutcome.ERROR))); + return new Result<>(); } } diff --git a/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml b/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml index c27d15b6..09c755f3 100644 --- a/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.client.controller.CreateClientController"> @@ -13,8 +14,7 @@ <Text text="Create Client" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml b/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml index 9936d19a..38bd2140 100644 --- a/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.client.controller.UpdateClientController"> @@ -13,8 +14,7 @@ <Text text="Update Client" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml index 5588d5b8..1b2657bd 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.factory.controller.CreateFactoryController"> @@ -13,13 +14,12 @@ <Text text="Create Factory" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 10px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml index 42763d80..7827c22c 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.factory.controller.UpdateFactoryController"> @@ -13,8 +14,7 @@ <Text text="Update Factory" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml b/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml index 3fa31e2f..251d5ad2 100644 --- a/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml @@ -3,9 +3,10 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - fx:controller="org.chainoptim.desktop.features.supplier.controller.CreateSupplierController" > + fx:controller="org.chainoptim.desktop.features.supplier.controller.CreateSupplierController"> <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> <VBox VBox.vgrow="ALWAYS" styleClass="form-container"> @@ -13,8 +14,7 @@ <Text text="Create Supplier" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml b/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml index 2f6e3fc7..2b17b5c9 100644 --- a/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml @@ -3,24 +3,27 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - fx:controller="org.chainoptim.desktop.features.supplier.controller.UpdateSupplierController" - styleClass="form-container"> + fx:controller="org.chainoptim.desktop.features.supplier.controller.UpdateSupplierController"> - <HBox styleClass="title-container"> - <Text text="Update Supplier" styleClass="form-title"/> - </HBox> + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <VBox VBox.vgrow="ALWAYS" styleClass="form-container"> + <HBox styleClass="title-container"> + <Text text="Update Supplier" styleClass="form-title"/> + </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> - <Label text="Location:" styleClass="form-label"/> - <StackPane fx:id="selectOrCreateLocationContainer"/> + <Label text="Location:" styleClass="form-label"/> + <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 10px 0px;"> - <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> - </HBox> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> + </HBox> + </VBox> + </ScrollPane> <StackPane fx:id="fallbackContainer" VBox.vgrow="ALWAYS"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml b/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml index b253dc07..b295c52a 100644 --- a/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.warehouse.controller.CreateWarehouseController"> @@ -13,8 +14,7 @@ <Text text="Create Warehouse" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> diff --git a/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml b/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml index 94a48710..eac00294 100644 --- a/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml @@ -3,6 +3,7 @@ <?import javafx.scene.layout.*?> <?import javafx.scene.text.Text?> +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.features.warehouse.controller.UpdateWarehouseController"> @@ -13,8 +14,7 @@ <Text text="Update Warehouse" styleClass="form-title"/> </HBox> - <Label text="Name *:" styleClass="form-label"/> - <TextField fx:id="nameField" styleClass="custom-text-field"/> + <FormField fx:id="nameFormField"/> <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> From 43329c3e24682719f1faf5d02ce69b999a6d1ef7 Mon Sep 17 00:00:00 2001 From: TudorOrban <130213626+TudorOrban@users.noreply.github.com> Date: Thu, 25 Apr 2024 23:27:45 +0300 Subject: [PATCH 10/10] Add Update Organization form --- .../main/service/NavigationServiceImpl.java | 1 + .../controller/OrganizationController.java | 27 ++-- .../UpdateOrganizationController.java | 135 ++++++++++++++++++ .../dto/UpdateOrganizationDTO.java | 16 +++ .../service/OrganizationService.java | 2 + .../service/OrganizationServiceImpl.java | 11 ++ src/main/resources/css/sidebar.css | 2 +- .../chainoptim/desktop/core/main/AppView.fxml | 2 +- .../organization/UpdateOrganizationView.fxml | 31 ++++ .../features/client/CreateClientView.fxml | 2 +- .../features/client/UpdateClientView.fxml | 2 +- .../features/factory/CreateFactoryView.fxml | 2 +- .../features/factory/UpdateFactoryView.fxml | 2 +- .../AddProductionRecordView.fxml | 2 +- .../CreateFactoryStageView.fxml | 2 +- .../UpdateFactoryStageView.fxml | 2 +- .../features/product/CreateProductView.fxml | 2 +- .../features/supplier/CreateSupplierView.fxml | 2 +- .../features/supplier/UpdateSupplierView.fxml | 2 +- .../warehouse/CreateWarehouseView.fxml | 2 +- .../warehouse/UpdateWarehouseView.fxml | 2 +- 21 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/chainoptim/desktop/core/organization/controller/UpdateOrganizationController.java create mode 100644 src/main/java/org/chainoptim/desktop/core/organization/dto/UpdateOrganizationDTO.java create mode 100644 src/main/resources/org/chainoptim/desktop/core/organization/UpdateOrganizationView.fxml 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 2233dfa1..4b3c5126 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 @@ -51,6 +51,7 @@ public NavigationServiceImpl(FXMLLoaderService fxmlLoaderService, Map.entry("Overview", "/org/chainoptim/desktop/core/overview/OverviewView.fxml"), Map.entry("Organization", "/org/chainoptim/desktop/core/organization/OrganizationView.fxml"), + Map.entry("Update-Organization", "/org/chainoptim/desktop/core/organization/UpdateOrganizationView.fxml"), Map.entry("Add-New-Members", "/org/chainoptim/desktop/core/organization/AddNewMembersView.fxml"), Map.entry("Products", "/org/chainoptim/desktop/features/product/ProductsView.fxml"), 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 3b86f7d3..b94ad98f 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 @@ -2,6 +2,7 @@ import org.chainoptim.desktop.core.abstraction.ControllerFactory; import org.chainoptim.desktop.core.context.TenantContext; +import org.chainoptim.desktop.core.main.service.NavigationService; import org.chainoptim.desktop.core.organization.model.CustomRole; import org.chainoptim.desktop.core.organization.model.Organization; import org.chainoptim.desktop.core.organization.model.OrganizationViewData; @@ -11,6 +12,7 @@ import org.chainoptim.desktop.shared.fallback.FallbackManager; import org.chainoptim.desktop.shared.httphandling.Result; import org.chainoptim.desktop.shared.util.DataReceiver; +import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; import org.chainoptim.desktop.shared.util.resourceloader.FXMLLoaderService; import com.google.inject.Inject; @@ -36,7 +38,8 @@ public class OrganizationController implements Initializable { // Services private final OrganizationService organizationService; private final CustomRoleService customRoleService; - private final FXMLLoaderService fxmlLoaderService; + private final NavigationService navigationService; + private final CommonViewsLoader commonViewsLoader; private final ControllerFactory controllerFactory; private final FallbackManager fallbackManager; @@ -69,21 +72,24 @@ public class OrganizationController implements Initializable { @Inject public OrganizationController(OrganizationService organizationService, - CustomRoleService customRoleService, - FXMLLoaderService fxmlLoaderService, + CustomRoleService customRoleService, + NavigationService navigationService, + CommonViewsLoader commonViewsLoader, ControllerFactory controllerFactory, FallbackManager fallbackManager) { this.organizationService = organizationService; this.customRoleService = customRoleService; - this.fxmlLoaderService = fxmlLoaderService; + this.navigationService = navigationService; + this.commonViewsLoader = commonViewsLoader; this.controllerFactory = controllerFactory; this.fallbackManager = fallbackManager; } @Override public void initialize(URL location, ResourceBundle resourceBundle) { - loadFallbackManager(); + commonViewsLoader.loadFallbackManager(fallbackContainer); setupListeners(); + User currentUser = TenantContext.getCurrentUser(); if (currentUser == null) { Platform.runLater(() -> fallbackManager.setLoading(false)); @@ -102,15 +108,6 @@ public void initialize(URL location, ResourceBundle resourceBundle) { loadCustomRoles(); // Multi-thread this as custom roles are not immediately needed } - 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) { @@ -223,6 +220,6 @@ private Result<Organization> handleOrganizationException(Throwable ex) { @FXML private void handleEditOrganization() { - System.out.println("Edit organization clicked"); + navigationService.switchView("Update-Organization", true); } } diff --git a/src/main/java/org/chainoptim/desktop/core/organization/controller/UpdateOrganizationController.java b/src/main/java/org/chainoptim/desktop/core/organization/controller/UpdateOrganizationController.java new file mode 100644 index 00000000..e77fba0a --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/core/organization/controller/UpdateOrganizationController.java @@ -0,0 +1,135 @@ +package org.chainoptim.desktop.core.organization.controller; + +import org.chainoptim.desktop.core.context.TenantContext; +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.organization.dto.UpdateOrganizationDTO; +import org.chainoptim.desktop.core.organization.model.Organization; +import org.chainoptim.desktop.core.organization.service.OrganizationService; +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.forms.FormField; +import org.chainoptim.desktop.shared.common.uielements.forms.ValidationException; +import org.chainoptim.desktop.shared.common.uielements.select.SelectOrCreateLocationController; +import org.chainoptim.desktop.shared.enums.OperationOutcome; +import org.chainoptim.desktop.shared.fallback.FallbackManager; +import org.chainoptim.desktop.shared.httphandling.Result; +import org.chainoptim.desktop.shared.toast.controller.ToastManager; +import org.chainoptim.desktop.shared.toast.model.ToastInfo; +import org.chainoptim.desktop.shared.util.resourceloader.CommonViewsLoader; +import com.google.inject.Inject; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.layout.StackPane; + +import java.net.URL; +import java.util.ResourceBundle; + +public class UpdateOrganizationController implements Initializable { + + // Services + private final OrganizationService organizationService; + private final NavigationService navigationService; + private final CommonViewsLoader commonViewsLoader; + private final ToastManager toastManager; + private final FallbackManager fallbackManager; + + // State + private Organization organization; + + // FXML + @FXML + private StackPane fallbackContainer; + @FXML + private FormField<String> nameFormField; + @FXML + private FormField<String> addressFormField; + @FXML + private FormField<String> contactInfoFormField; + + @Inject + public UpdateOrganizationController( + OrganizationService organizationService, + NavigationService navigationService, + CommonViewsLoader commonViewsLoader, + ToastManager toastManager, + FallbackManager fallbackManager) { + this.organizationService = organizationService; + this.navigationService = navigationService; + this.commonViewsLoader = commonViewsLoader; + this.toastManager = toastManager; + this.fallbackManager = fallbackManager; + } + + @FXML + public void initialize(URL location, ResourceBundle resources) { + this.organization = TenantContext.getCurrentUser().getOrganization(); + commonViewsLoader.loadFallbackManager(fallbackContainer); + initializeFormFields(); + } + + private void initializeFormFields() { + nameFormField.initialize(String::new, "Name", true, organization.getName(), "Your input is not valid"); + addressFormField.initialize(String::new, "Address", true, organization.getAddress(), "Your input is not valid"); + contactInfoFormField.initialize(String::new, "Contact Info", true, organization.getContactInfo(), "Your input is not valid"); + } + + @FXML + private void handleSubmit() { + UpdateOrganizationDTO organizationDTO = getUpdateOrganizationDTO(); + if (organizationDTO == null) return; + System.out.println(organizationDTO); + + fallbackManager.reset(); + fallbackManager.setLoading(true); + + organizationService.updateOrganization(organizationDTO) + .thenApply(this::handleUpdateOrganizationResponse) + .exceptionally(this::handleUpdateOrganizationException); + } + + private UpdateOrganizationDTO getUpdateOrganizationDTO() { + UpdateOrganizationDTO organizationDTO = new UpdateOrganizationDTO(); + organizationDTO.setId(organization.getId()); + organizationDTO.setId(organizationDTO.getId()); + try { + organizationDTO.setName(nameFormField.handleSubmit()); + organizationDTO.setAddress(addressFormField.handleSubmit()); + organizationDTO.setContactInfo(contactInfoFormField.handleSubmit()); + } catch (ValidationException e) { + return null; + } + + return organizationDTO; + } + + private Result<Organization> handleUpdateOrganizationResponse(Result<Organization> result) { + Platform.runLater(() -> { + fallbackManager.setLoading(false); + if (result.getError() != null) { + toastManager.addToast(new ToastInfo( + "Error", "Failed to update organization.", OperationOutcome.ERROR)); + return; + } + toastManager.addToast(new ToastInfo( + "Success", "Organization updated successfully.", OperationOutcome.SUCCESS)); + + // Manage navigation, invalidating previous warehouse cache + String organizationPage = "Organization"; + NavigationServiceImpl.invalidateViewCache(organizationPage); + navigationService.switchView(organizationPage, true); + }); + return result; + } + + private Result<Organization> handleUpdateOrganizationException(Throwable ex) { + Platform.runLater(() -> toastManager.addToast(new ToastInfo( + "An error occurred.", "Failed to update organization.", OperationOutcome.ERROR))); + return new Result<>(); + } +} + diff --git a/src/main/java/org/chainoptim/desktop/core/organization/dto/UpdateOrganizationDTO.java b/src/main/java/org/chainoptim/desktop/core/organization/dto/UpdateOrganizationDTO.java new file mode 100644 index 00000000..fac9035c --- /dev/null +++ b/src/main/java/org/chainoptim/desktop/core/organization/dto/UpdateOrganizationDTO.java @@ -0,0 +1,16 @@ +package org.chainoptim.desktop.core.organization.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UpdateOrganizationDTO { + + private Integer id; + private String name; + private String address; + private String contactInfo; +} 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 59cb929a..52f86c8f 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,5 +1,6 @@ package org.chainoptim.desktop.core.organization.service; +import org.chainoptim.desktop.core.organization.dto.UpdateOrganizationDTO; import org.chainoptim.desktop.core.organization.model.Organization; import org.chainoptim.desktop.core.user.model.User; import org.chainoptim.desktop.shared.httphandling.Result; @@ -10,5 +11,6 @@ public interface OrganizationService { CompletableFuture<Result<Organization>> getOrganizationById(Integer organizationId, boolean includeUsers); + CompletableFuture<Result<Organization>> updateOrganization(UpdateOrganizationDTO organizationDTO); } 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 index 4ede8f4b..5c10e4ec 100644 --- a/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java +++ b/src/main/java/org/chainoptim/desktop/core/organization/service/OrganizationServiceImpl.java @@ -1,7 +1,9 @@ package org.chainoptim.desktop.core.organization.service; +import org.chainoptim.desktop.core.organization.dto.UpdateOrganizationDTO; import org.chainoptim.desktop.core.organization.model.Organization; import org.chainoptim.desktop.core.user.service.TokenManager; +import org.chainoptim.desktop.shared.httphandling.HttpMethod; import org.chainoptim.desktop.shared.httphandling.RequestBuilder; import org.chainoptim.desktop.shared.httphandling.RequestHandler; import org.chainoptim.desktop.shared.httphandling.Result; @@ -34,4 +36,13 @@ public CompletableFuture<Result<Organization>> getOrganizationById(Integer organ return requestHandler.sendRequest(request, new TypeReference<Organization>() {}); } + + public CompletableFuture<Result<Organization>> updateOrganization(UpdateOrganizationDTO organizationDTO) { + String routeAddress = "http://localhost:8080/api/v1/organizations/update"; + + HttpRequest request = requestBuilder.buildWriteRequest( + HttpMethod.POST, routeAddress, tokenManager.getToken(), organizationDTO); + + return requestHandler.sendRequest(request, new TypeReference<Organization>() {}); + } } diff --git a/src/main/resources/css/sidebar.css b/src/main/resources/css/sidebar.css index 8b019b40..9a96e1db 100644 --- a/src/main/resources/css/sidebar.css +++ b/src/main/resources/css/sidebar.css @@ -21,7 +21,7 @@ } .sidebar-title-container-collapsed { - -fx-padding: 16px 12px; + -fx-padding: 16px 12px 24px 12px; } .sidebar-title { 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 ec0e3852..df43b623 100644 --- a/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml +++ b/src/main/resources/org/chainoptim/desktop/core/main/AppView.fxml @@ -4,7 +4,7 @@ <BorderPane xmlns = "http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.chainoptim.desktop.core.main.controller.AppController" - style="-fx-background-color: #ffffff; -fx-border-width: 1px 0px; -fx-border-color: #c0c0c0;"> + style="-fx-background-color: #ffffff; -fx-border-width: 1px 0px 0px 0px; -fx-border-color: #c0c0c0;"> <!-- Sidebar --> <left> diff --git a/src/main/resources/org/chainoptim/desktop/core/organization/UpdateOrganizationView.fxml b/src/main/resources/org/chainoptim/desktop/core/organization/UpdateOrganizationView.fxml new file mode 100644 index 00000000..725f34a6 --- /dev/null +++ b/src/main/resources/org/chainoptim/desktop/core/organization/UpdateOrganizationView.fxml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?import javafx.scene.control.*?> +<?import javafx.scene.layout.*?> +<?import javafx.scene.text.Text?> + +<?import org.chainoptim.desktop.shared.common.uielements.forms.FormField?> +<VBox xmlns="http://javafx.com/javafx" + xmlns:fx="http://javafx.com/fxml" + fx:controller="org.chainoptim.desktop.core.organization.controller.UpdateOrganizationController"> + + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <VBox VBox.vgrow="ALWAYS" styleClass="form-container"> + <HBox styleClass="title-container"> + <Text text="Update Organization" styleClass="form-title"/> + </HBox> + + <FormField fx:id="nameFormField"/> + + <FormField fx:id="addressFormField"/> + + <FormField fx:id="contactInfoFormField"/> + + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> + <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> + </HBox> + </VBox> + </ScrollPane> + + <StackPane fx:id="fallbackContainer" VBox.vgrow="ALWAYS"/> + +</VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml b/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml index 09c755f3..d1d29d8a 100644 --- a/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/client/CreateClientView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml b/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml index 38bd2140..72235798 100644 --- a/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/client/UpdateClientView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml index 1b2657bd..9b1a588f 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/CreateFactoryView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml index 7827c22c..c412a1f2 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/UpdateFactoryView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/AddProductionRecordView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/AddProductionRecordView.fxml index ac599eff..1fced178 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/AddProductionRecordView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/AddProductionRecordView.fxml @@ -44,7 +44,7 @@ <Label text="Allocations" styleClass="general-label-extra-large" style="-fx-padding: 16px 0px 0px 0px;"/> <VBox fx:id="allocationsVBox"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Add Record" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/CreateFactoryStageView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/CreateFactoryStageView.fxml index d364f4ad..2cf18dee 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/CreateFactoryStageView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/CreateFactoryStageView.fxml @@ -35,7 +35,7 @@ <Label text="Minimum Required Capacity:" styleClass="form-label"/> <TextField fx:id="minimumRequiredCapacityField" styleClass="custom-text-field"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Add Stage" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/UpdateFactoryStageView.fxml b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/UpdateFactoryStageView.fxml index 3b758ed9..4d7465bf 100644 --- a/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/UpdateFactoryStageView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/factory/factoryproduction/UpdateFactoryStageView.fxml @@ -30,7 +30,7 @@ <Label text="Minimum Required Capacity:" styleClass="form-label"/> <TextField fx:id="minimumRequiredCapacityField" styleClass="custom-text-field"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Update Stage" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml b/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml index c970307a..ea4b1a83 100644 --- a/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/product/CreateProductView.fxml @@ -23,7 +23,7 @@ <Label text="Unit of Measurement:" styleClass="form-label"/> <StackPane fx:id="unitOfMeasurementContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml b/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml index 251d5ad2..eac006be 100644 --- a/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/supplier/CreateSupplierView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml b/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml index 2b17b5c9..09e5d070 100644 --- a/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/supplier/UpdateSupplierView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml b/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml index b295c52a..8a907336 100644 --- a/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/warehouse/CreateWarehouseView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Create" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox> diff --git a/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml b/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml index eac00294..0861a9d2 100644 --- a/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml +++ b/src/main/resources/org/chainoptim/desktop/features/warehouse/UpdateWarehouseView.fxml @@ -19,7 +19,7 @@ <Label text="Location:" styleClass="form-label"/> <StackPane fx:id="selectOrCreateLocationContainer"/> - <HBox alignment="TOP_RIGHT" style="-fx-padding: 16px 0px 0px 0px;"> + <HBox alignment="TOP_RIGHT" style="-fx-padding: 20px 0px 0px 0px;"> <Button text="Update" onAction="#handleSubmit" styleClass="standard-write-button"/> </HBox> </VBox>