From 8b473c19f84a071b6b5811bee95837f6cae9c299 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 15 Apr 2026 15:49:47 -0600 Subject: [PATCH 1/2] fix loyalty points display --- .../detailfragments/SaleDetailFragment.java | 11 ++++------- .../com/petshop/backend/dto/sale/SaleResponse.java | 9 +++++++++ .../main/java/com/petshop/backend/entity/Sale.java | 11 +++++++++++ .../java/com/petshop/backend/service/SaleService.java | 3 +++ .../petshopdesktop/controllers/SaleController.java | 4 +++- .../dialogcontrollers/SaleDetailDialogController.java | 6 ++++++ .../org/example/petshopdesktop/models/SaleDetail.java | 8 +++++++- .../dialogviews/sale-detail-dialog-view.fxml | 2 +- 8 files changed, 44 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java index 4963b272..e592fb7d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java @@ -206,15 +206,12 @@ public class SaleDetailFragment extends Fragment { binding.llEmployeeDiscount.setVisibility(View.GONE); } - if (sale.getPointsDiscountAmount() != null && sale.getPointsDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { - binding.llLoyaltyDiscount.setVisibility(View.VISIBLE); - binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getPointsDiscountAmount())); - if (sale.getPointsUsed() != null) { - binding.tvLoyaltyDiscountLabel.setText("Loyalty Discount (" + sale.getPointsUsed() + " pts):"); - } - } else if (sale.getLoyaltyDiscountAmount() != null && sale.getLoyaltyDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { + if (sale.getLoyaltyDiscountAmount() != null && sale.getLoyaltyDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { binding.llLoyaltyDiscount.setVisibility(View.VISIBLE); binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getLoyaltyDiscountAmount())); + if (sale.getPointsUsed() != null && sale.getPointsUsed() > 0) { + binding.tvLoyaltyDiscountLabel.setText("Loyalty Discount (" + sale.getPointsUsed() + " pts):"); + } } else { binding.llLoyaltyDiscount.setVisibility(View.GONE); } diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index 8f2b4fe5..af116212 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -20,6 +20,7 @@ public class SaleResponse { private BigDecimal employeeDiscountAmount; private BigDecimal loyaltyDiscountAmount; private Integer pointsEarned; + private Integer pointsUsed; private String channel; private Long couponId; private Long cartId; @@ -145,6 +146,14 @@ public class SaleResponse { this.pointsEarned = pointsEarned; } + public Integer getPointsUsed() { + return pointsUsed; + } + + public void setPointsUsed(Integer pointsUsed) { + this.pointsUsed = pointsUsed; + } + public String getChannel() { return channel; } diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java index e61204fd..b052e17d 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -73,6 +73,9 @@ public class Sale { @Column(nullable = false) private Integer pointsEarned = 0; + @Column(nullable = false) + private Integer pointsUsed = 0; + @OneToMany(mappedBy = "sale", cascade = CascadeType.ALL) private List items = new ArrayList<>(); @@ -224,6 +227,14 @@ public class Sale { this.pointsEarned = pointsEarned; } + public Integer getPointsUsed() { + return pointsUsed; + } + + public void setPointsUsed(Integer pointsUsed) { + this.pointsUsed = pointsUsed; + } + public List getItems() { return items; } diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index b53a4c4c..bff502a9 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -200,6 +200,7 @@ public class SaleService { sale.setEmployeeDiscountAmount(BigDecimal.ZERO); sale.setLoyaltyDiscountAmount(loyaltyDiscountRefunded); sale.setPointsEarned(0); + sale.setPointsUsed(0); } else { if (request.getItems() == null || request.getItems().isEmpty()) { throw new BusinessException("At least one item is required"); @@ -254,6 +255,7 @@ public class SaleService { pointsDeducted = toPointsUsed(loyaltyDiscount); } sale.setLoyaltyDiscountAmount(loyaltyDiscount); + sale.setPointsUsed(pointsDeducted); BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount); sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO)); @@ -376,6 +378,7 @@ public class SaleService { response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount()); response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount()); response.setPointsEarned(sale.getPointsEarned()); + response.setPointsUsed(sale.getPointsUsed()); response.setChannel(sale.getChannel()); if (sale.getCoupon() != null) { response.setCouponId(sale.getCoupon().getCouponId()); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java index 8eaaf7af..7e71dc32 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java @@ -841,6 +841,7 @@ public class SaleController { double subtotal = sale.getSubtotalAmount() != null ? sale.getSubtotalAmount().doubleValue() : 0.0; double couponDiscount = sale.getCouponDiscountAmount() != null ? sale.getCouponDiscountAmount().doubleValue() : 0.0; double loyaltyDiscount = sale.getLoyaltyDiscountAmount() != null ? sale.getLoyaltyDiscountAmount().doubleValue() : 0.0; + int pointsUsed = sale.getPointsUsed() != null ? sale.getPointsUsed() : 0; return new SaleDetail( sale.getSaleId().intValue(), sale.getSaleDate(), @@ -852,7 +853,8 @@ public class SaleController { sale.getCustomerName(), subtotal, couponDiscount, - loyaltyDiscount + loyaltyDiscount, + pointsUsed ); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java index 332be5e8..4774c14d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java @@ -33,6 +33,7 @@ public class SaleDetailDialogController { @FXML private javafx.scene.layout.HBox hbDetailCouponDiscount; @FXML private Label lblDetailCouponDiscount; @FXML private javafx.scene.layout.HBox hbDetailLoyaltyDiscount; + @FXML private Label lblDetailLoyaltyDiscountTitle; @FXML private Label lblDetailLoyaltyDiscount; @FXML private Label lblTotal; @FXML private Button btnRefund; @@ -86,6 +87,11 @@ public class SaleDetailDialogController { if (sale.getLoyaltyDiscountAmount() > 0.001) { lblDetailLoyaltyDiscount.setText("-" + currency.format(sale.getLoyaltyDiscountAmount())); + if (sale.getPointsUsed() > 0) { + lblDetailLoyaltyDiscountTitle.setText("Loyalty Discount (" + sale.getPointsUsed() + " pts):"); + } else { + lblDetailLoyaltyDiscountTitle.setText("Loyalty Discount:"); + } hbDetailLoyaltyDiscount.setVisible(true); hbDetailLoyaltyDiscount.setManaged(true); } else { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java b/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java index 13f500c4..63d3cf47 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java @@ -15,8 +15,9 @@ public class SaleDetail { private final double subtotalAmount; private final double couponDiscountAmount; private final double loyaltyDiscountAmount; + private final int pointsUsed; - public SaleDetail(int saleId, LocalDateTime saleDate, double totalAmount, String paymentMethod, String employeeName, boolean refund, ObservableList items, String customerName, double subtotalAmount, double couponDiscountAmount, double loyaltyDiscountAmount) { + public SaleDetail(int saleId, LocalDateTime saleDate, double totalAmount, String paymentMethod, String employeeName, boolean refund, ObservableList items, String customerName, double subtotalAmount, double couponDiscountAmount, double loyaltyDiscountAmount, int pointsUsed) { this.saleId = saleId; this.saleDate = saleDate; this.totalAmount = totalAmount; @@ -28,6 +29,7 @@ public class SaleDetail { this.subtotalAmount = subtotalAmount; this.couponDiscountAmount = couponDiscountAmount; this.loyaltyDiscountAmount = loyaltyDiscountAmount; + this.pointsUsed = pointsUsed; } public int getSaleId() { @@ -74,6 +76,10 @@ public class SaleDetail { return loyaltyDiscountAmount; } + public int getPointsUsed() { + return pointsUsed; + } + public static class SaleDetailItem { private final int prodId; private final String productName; diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml index 4365bef7..45a3f440 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml @@ -69,7 +69,7 @@ - + -- 2.49.1 From 2cd04d63e13241ac60c381cc36612d0c217284f3 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 15 Apr 2026 15:55:49 -0600 Subject: [PATCH 2/2] user avatar in edit dialogs --- .../controller/UserAvatarController.java | 75 ++++++++++++++++++ .../backend/dto/user/UserResponse.java | 9 +++ .../petshop/backend/service/UserService.java | 1 + .../api/dto/employee/EmployeeResponse.java | 3 + .../api/dto/user/UserResponse.java | 9 +++ .../petshopdesktop/api/endpoints/UserApi.java | 9 +++ .../CustomerEditDialogController.java | 77 +++++++++++++++++++ .../StaffEditDialogController.java | 76 ++++++++++++++++++ .../customer-edit-dialog-view.fxml | 18 +++++ .../dialogviews/staff-edit-dialog-view.fxml | 18 +++++ 10 files changed, 295 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java index b236cf8b..68ef41ef 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java +++ b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java @@ -4,12 +4,21 @@ import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AvatarStorageService; import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; @RestController @RequestMapping("/api/v1/users") @@ -40,4 +49,70 @@ public class UserAvatarController { return ResponseEntity.notFound().build(); } } + + @PostMapping("/{userId}/avatar") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity uploadUserAvatar(@PathVariable Long userId, @RequestParam("avatar") MultipartFile file) { + User user = userRepository.findById(userId).orElse(null); + if (user == null) { + return ResponseEntity.notFound().build(); + } + + if (file.isEmpty()) { + Map error = new HashMap<>(); + error.put("message", "Please select a file to upload"); + return ResponseEntity.badRequest().body(error); + } + + if (file.getSize() > 5 * 1024 * 1024) { + Map error = new HashMap<>(); + error.put("message", "File size must not exceed 5MB"); + return ResponseEntity.badRequest().body(error); + } + + String contentType = file.getContentType(); + if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) { + Map error = new HashMap<>(); + error.put("message", "Only JPG, PNG, and GIF images are allowed"); + return ResponseEntity.badRequest().body(error); + } + + try { + avatarStorageService.deleteAvatar(user); + String avatarPath = avatarStorageService.storeAvatar(file); + user.setAvatarUrl(avatarPath); + userRepository.save(user); + + Map result = new HashMap<>(); + result.put("avatarUrl", avatarStorageService.toOwnerAvatarUrl(user)); + result.put("message", "Avatar uploaded successfully"); + return ResponseEntity.ok(result); + } catch (IOException e) { + Map error = new HashMap<>(); + error.put("message", "Failed to upload avatar: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } + + @DeleteMapping("/{userId}/avatar") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity deleteUserAvatar(@PathVariable Long userId) { + User user = userRepository.findById(userId).orElse(null); + if (user == null) { + return ResponseEntity.notFound().build(); + } + + try { + avatarStorageService.deleteAvatar(user); + user.setAvatarUrl(null); + userRepository.save(user); + Map result = new HashMap<>(); + result.put("message", "Avatar removed successfully"); + return ResponseEntity.ok(result); + } catch (IOException e) { + Map error = new HashMap<>(); + error.put("message", "Failed to remove avatar: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } } diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java index 6c4d15b5..6c8ffd68 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -16,6 +16,7 @@ public class UserResponse { private Long primaryStoreId; private Integer loyaltyPoints; private Boolean active; + private String avatarUrl; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -118,6 +119,14 @@ public class UserResponse { this.active = active; } + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/backend/src/main/java/com/petshop/backend/service/UserService.java b/backend/src/main/java/com/petshop/backend/service/UserService.java index 4cf83d35..6eb8c2fa 100644 --- a/backend/src/main/java/com/petshop/backend/service/UserService.java +++ b/backend/src/main/java/com/petshop/backend/service/UserService.java @@ -205,6 +205,7 @@ public class UserService { response.setPrimaryStoreId(user.getPrimaryStore() != null ? user.getPrimaryStore().getStoreId() : null); response.setLoyaltyPoints(user.getLoyaltyPoints()); response.setActive(user.getActive()); + response.setAvatarUrl(user.getAvatarUrl() != null ? "/api/v1/users/" + user.getId() + "/avatar/file" : null); response.setCreatedAt(user.getCreatedAt()); response.setUpdatedAt(user.getUpdatedAt()); return response; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java index f9ce7f96..c6a32d23 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java @@ -16,6 +16,7 @@ public class EmployeeResponse { private String staffRole; private Long primaryStoreId; private Boolean active; + private String avatarUrl; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -45,6 +46,8 @@ public class EmployeeResponse { public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; } public Boolean getActive() { return active; } public void setActive(Boolean active) { this.active = active; } + public String getAvatarUrl() { return avatarUrl; } + public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java index f9fc4eac..2e1daa0e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java @@ -11,6 +11,7 @@ public class UserResponse { private String role; private Boolean active; private Integer loyaltyPoints; + private String avatarUrl; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -81,6 +82,14 @@ public class UserResponse { this.loyaltyPoints = loyaltyPoints; } + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java index e927f03c..27439ee9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java @@ -8,6 +8,7 @@ import org.example.petshopdesktop.api.dto.user.UserResponse; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.List; public class UserApi { @@ -45,4 +46,12 @@ public class UserApi { public UserResponse updateUser(Long id, UserRequest request) throws Exception { return apiClient.put("/api/v1/users/" + id, request, UserResponse.class); } + + public void uploadUserAvatar(Long userId, Path filePath) throws Exception { + apiClient.postMultipart("/api/v1/users/" + userId + "/avatar", "avatar", filePath, Object.class); + } + + public void deleteUserAvatar(Long userId) throws Exception { + apiClient.delete("/api/v1/users/" + userId + "/avatar"); + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java index 292c21be..92c800e3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java @@ -9,15 +9,21 @@ import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; import javafx.stage.Stage; import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.api.dto.user.UserRequest; import org.example.petshopdesktop.api.dto.user.UserResponse; import org.example.petshopdesktop.api.endpoints.CustomerApi; +import org.example.petshopdesktop.api.endpoints.UserApi; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; +import org.example.petshopdesktop.util.FilePickerSupport; import org.example.petshopdesktop.util.TextFieldFormatSupport; +import java.io.File; + public class CustomerEditDialogController { @FXML private TextField txtFirstName; @@ -32,7 +38,15 @@ public class CustomerEditDialogController { @FXML private Label lblError; @FXML private Button btnSave; + @FXML private ImageView imgAvatarPreview; + @FXML private Label lblAvatarStatus; + @FXML private Button btnChangeAvatar; + @FXML private Button btnRemoveAvatar; + private UserResponse customer; + private File selectedAvatarFile; + private String currentAvatarUrl; + private boolean removeAvatarRequested; @FXML void initialize() { @@ -41,6 +55,9 @@ public class CustomerEditDialogController { boolean isAdmin = UserSession.getInstance().isAdmin(); txtLoyaltyPoints.setDisable(!isAdmin); + + btnChangeAvatar.setOnMouseClicked(e -> handleChangeAvatar()); + btnRemoveAvatar.setOnMouseClicked(e -> handleRemoveAvatar()); } public void setCustomer(UserResponse user) { @@ -55,6 +72,65 @@ public class CustomerEditDialogController { cbActive.setValue(Boolean.TRUE.equals(user.getActive()) ? "Active" : "Inactive"); int pts = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0; txtLoyaltyPoints.setText(String.valueOf(pts)); + + currentAvatarUrl = user.getAvatarUrl(); + refreshAvatarPreview(); + } + + private void handleChangeAvatar() { + File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow()); + if (file == null) return; + selectedAvatarFile = file; + removeAvatarRequested = false; + lblAvatarStatus.setText("Selected: " + file.getName()); + DesktopImageSupport.loadImageInto(imgAvatarPreview, file.toURI().toString(), 90, 90); + btnRemoveAvatar.setDisable(false); + } + + private void handleRemoveAvatar() { + selectedAvatarFile = null; + removeAvatarRequested = true; + currentAvatarUrl = null; + refreshAvatarPreview(); + } + + private void applyAvatarChanges(Long userId) throws Exception { + String previousAvatarUrl = currentAvatarUrl; + if (removeAvatarRequested) { + try { + UserApi.getInstance().deleteUserAvatar(userId); + } catch (Exception ignored) { + } + } + if (selectedAvatarFile != null) { + UserApi.getInstance().uploadUserAvatar(userId, selectedAvatarFile.toPath()); + currentAvatarUrl = "/api/v1/users/" + userId + "/avatar/file"; + } else if (removeAvatarRequested) { + currentAvatarUrl = null; + } + DesktopImageSupport.evict(previousAvatarUrl); + DesktopImageSupport.evict(currentAvatarUrl); + selectedAvatarFile = null; + removeAvatarRequested = false; + } + + private void refreshAvatarPreview() { + if (imgAvatarPreview == null || lblAvatarStatus == null || btnRemoveAvatar == null) return; + imgAvatarPreview.setImage(null); + if (selectedAvatarFile != null) { + lblAvatarStatus.setText("Selected: " + selectedAvatarFile.getName()); + DesktopImageSupport.loadImageInto(imgAvatarPreview, selectedAvatarFile.toURI().toString(), 90, 90); + btnRemoveAvatar.setDisable(false); + return; + } + if (currentAvatarUrl != null && !currentAvatarUrl.isBlank()) { + lblAvatarStatus.setText("Current avatar loaded"); + DesktopImageSupport.loadImageInto(imgAvatarPreview, currentAvatarUrl, 90, 90); + btnRemoveAvatar.setDisable(false); + return; + } + lblAvatarStatus.setText("No avatar"); + btnRemoveAvatar.setDisable(true); } private String[] splitFullName(String fullName) { @@ -148,6 +224,7 @@ public class CustomerEditDialogController { if (finalLoyaltyPoints != null) request.setLoyaltyPoints(finalLoyaltyPoints); CustomerApi.getInstance().updateCustomer(customer.getId(), request); + applyAvatarChanges(customer.getId()); Platform.runLater(this::close); } catch (Exception e) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java index 23a6a00a..86a8603a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -10,6 +10,7 @@ import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; import javafx.stage.Stage; import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.api.dto.common.DropdownOption; @@ -17,9 +18,13 @@ import org.example.petshopdesktop.api.dto.employee.EmployeeRequest; import org.example.petshopdesktop.api.dto.employee.EmployeeResponse; import org.example.petshopdesktop.api.endpoints.DropdownApi; import org.example.petshopdesktop.api.endpoints.EmployeeApi; +import org.example.petshopdesktop.api.endpoints.UserApi; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; +import org.example.petshopdesktop.util.FilePickerSupport; import org.example.petshopdesktop.util.TextFieldFormatSupport; +import java.io.File; import java.util.List; public class StaffEditDialogController { @@ -37,8 +42,16 @@ public class StaffEditDialogController { @FXML private Label lblError; @FXML private Button btnSave; + @FXML private ImageView imgAvatarPreview; + @FXML private Label lblAvatarStatus; + @FXML private Button btnChangeAvatar; + @FXML private Button btnRemoveAvatar; + private EmployeeResponse employee; private Long pendingStoreId = null; + private File selectedAvatarFile; + private String currentAvatarUrl; + private boolean removeAvatarRequested; @FXML void initialize() { @@ -60,6 +73,9 @@ public class StaffEditDialogController { } }); + btnChangeAvatar.setOnMouseClicked(e -> handleChangeAvatar()); + btnRemoveAvatar.setOnMouseClicked(e -> handleRemoveAvatar()); + loadStores(); } @@ -110,6 +126,65 @@ public class StaffEditDialogController { pendingStoreId = emp.getPrimaryStoreId(); applyPendingStore(); + + currentAvatarUrl = emp.getAvatarUrl(); + refreshAvatarPreview(); + } + + private void handleChangeAvatar() { + File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow()); + if (file == null) return; + selectedAvatarFile = file; + removeAvatarRequested = false; + lblAvatarStatus.setText("Selected: " + file.getName()); + DesktopImageSupport.loadImageInto(imgAvatarPreview, file.toURI().toString(), 90, 90); + btnRemoveAvatar.setDisable(false); + } + + private void handleRemoveAvatar() { + selectedAvatarFile = null; + removeAvatarRequested = true; + currentAvatarUrl = null; + refreshAvatarPreview(); + } + + private void applyAvatarChanges(Long userId) throws Exception { + String previousAvatarUrl = currentAvatarUrl; + if (removeAvatarRequested) { + try { + UserApi.getInstance().deleteUserAvatar(userId); + } catch (Exception ignored) { + } + } + if (selectedAvatarFile != null) { + UserApi.getInstance().uploadUserAvatar(userId, selectedAvatarFile.toPath()); + currentAvatarUrl = "/api/v1/users/" + userId + "/avatar/file"; + } else if (removeAvatarRequested) { + currentAvatarUrl = null; + } + DesktopImageSupport.evict(previousAvatarUrl); + DesktopImageSupport.evict(currentAvatarUrl); + selectedAvatarFile = null; + removeAvatarRequested = false; + } + + private void refreshAvatarPreview() { + if (imgAvatarPreview == null || lblAvatarStatus == null || btnRemoveAvatar == null) return; + imgAvatarPreview.setImage(null); + if (selectedAvatarFile != null) { + lblAvatarStatus.setText("Selected: " + selectedAvatarFile.getName()); + DesktopImageSupport.loadImageInto(imgAvatarPreview, selectedAvatarFile.toURI().toString(), 90, 90); + btnRemoveAvatar.setDisable(false); + return; + } + if (currentAvatarUrl != null && !currentAvatarUrl.isBlank()) { + lblAvatarStatus.setText("Current avatar loaded"); + DesktopImageSupport.loadImageInto(imgAvatarPreview, currentAvatarUrl, 90, 90); + btnRemoveAvatar.setDisable(false); + return; + } + lblAvatarStatus.setText("No avatar"); + btnRemoveAvatar.setDisable(true); } private String[] splitFullName(String fullName) { @@ -193,6 +268,7 @@ public class StaffEditDialogController { request.setPrimaryStoreId(storeId); EmployeeApi.getInstance().updateEmployee(employee.getId(), request); + applyAvatarChanges(employee.getUserId()); Platform.runLater(this::close); } catch (Exception e) { diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml index fe0b34d7..f089cb06 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml @@ -6,6 +6,7 @@ + @@ -24,6 +25,23 @@