From 326182aeef0c4cc1ff331006e3daab6ecc9593b4 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Sun, 12 Apr 2026 23:47:22 -0600 Subject: [PATCH] added coupons to desktop app --- desktop/src/main/java/module-info.java | 1 + .../api/dto/coupon/CouponRequest.java | 38 +++ .../api/dto/coupon/CouponResponse.java | 52 ++++ .../api/endpoints/CouponApi.java | 42 +++ .../controllers/CouponController.java | 246 ++++++++++++++++++ .../controllers/MainLayoutController.java | 15 ++ .../CouponDialogController.java | 203 +++++++++++++++ .../dialogviews/coupon-dialog-view.fxml | 170 ++++++++++++ .../petshopdesktop/main-layout-view.fxml | 8 + .../modelviews/coupon-view.fxml | 74 ++++++ 10 files changed, 849 insertions(+) create mode 100644 desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java create mode 100644 desktop/src/main/resources/org/example/petshopdesktop/dialogviews/coupon-dialog-view.fxml create mode 100644 desktop/src/main/resources/org/example/petshopdesktop/modelviews/coupon-view.fxml diff --git a/desktop/src/main/java/module-info.java b/desktop/src/main/java/module-info.java index ecd94fd5..a13de731 100644 --- a/desktop/src/main/java/module-info.java +++ b/desktop/src/main/java/module-info.java @@ -35,6 +35,7 @@ module org.example.petshopdesktop { opens org.example.petshopdesktop.api.dto.analytics to com.fasterxml.jackson.databind; opens org.example.petshopdesktop.api.dto.purchaseorder to com.fasterxml.jackson.databind; opens org.example.petshopdesktop.api.dto.activity to com.fasterxml.jackson.databind; + opens org.example.petshopdesktop.api.dto.coupon to com.fasterxml.jackson.databind, javafx.base; exports org.example.petshopdesktop; exports org.example.petshopdesktop.controllers; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java new file mode 100644 index 00000000..dc11d55c --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java @@ -0,0 +1,38 @@ +package org.example.petshopdesktop.api.dto.coupon; + +import java.math.BigDecimal; + +public class CouponRequest { + private String couponCode; + private String discountType; + private BigDecimal discountValue; + private BigDecimal minOrderAmount; + private Boolean active; + private String startsAt; + private String endsAt; + private Integer usageLimit; + + public String getCouponCode() { return couponCode; } + public void setCouponCode(String couponCode) { this.couponCode = couponCode; } + + public String getDiscountType() { return discountType; } + public void setDiscountType(String discountType) { this.discountType = discountType; } + + public BigDecimal getDiscountValue() { return discountValue; } + public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; } + + public BigDecimal getMinOrderAmount() { return minOrderAmount; } + public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; } + + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } + + public String getStartsAt() { return startsAt; } + public void setStartsAt(String startsAt) { this.startsAt = startsAt; } + + public String getEndsAt() { return endsAt; } + public void setEndsAt(String endsAt) { this.endsAt = endsAt; } + + public Integer getUsageLimit() { return usageLimit; } + public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java new file mode 100644 index 00000000..e1c23f0d --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java @@ -0,0 +1,52 @@ +package org.example.petshopdesktop.api.dto.coupon; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.math.BigDecimal; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CouponResponse { + private Long couponId; + private String couponCode; + private String discountType; + private BigDecimal discountValue; + private BigDecimal minOrderAmount; + private Boolean active; + private String startsAt; + private String endsAt; + private Integer usageLimit; + private String createdAt; + private String updatedAt; + + public Long getCouponId() { return couponId; } + public void setCouponId(Long couponId) { this.couponId = couponId; } + + public String getCouponCode() { return couponCode; } + public void setCouponCode(String couponCode) { this.couponCode = couponCode; } + + public String getDiscountType() { return discountType; } + public void setDiscountType(String discountType) { this.discountType = discountType; } + + public BigDecimal getDiscountValue() { return discountValue; } + public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; } + + public BigDecimal getMinOrderAmount() { return minOrderAmount; } + public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; } + + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } + + public String getStartsAt() { return startsAt; } + public void setStartsAt(String startsAt) { this.startsAt = startsAt; } + + public String getEndsAt() { return endsAt; } + public void setEndsAt(String endsAt) { this.endsAt = endsAt; } + + public Integer getUsageLimit() { return usageLimit; } + public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; } + + public String getCreatedAt() { return createdAt; } + public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } + + public String getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java new file mode 100644 index 00000000..b409b5d1 --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java @@ -0,0 +1,42 @@ +package org.example.petshopdesktop.api.endpoints; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.example.petshopdesktop.api.ApiClient; +import org.example.petshopdesktop.api.dto.common.PageResponse; +import org.example.petshopdesktop.api.dto.coupon.CouponRequest; +import org.example.petshopdesktop.api.dto.coupon.CouponResponse; + +import java.util.List; + +public class CouponApi { + private static final CouponApi INSTANCE = new CouponApi(); + private final ApiClient apiClient; + + private CouponApi() { + apiClient = ApiClient.getInstance(); + } + + public static CouponApi getInstance() { + return INSTANCE; + } + + public List listCoupons() throws Exception { + String response = apiClient.getRawResponse("/api/v1/coupons?page=0&size=1000"); + PageResponse page = apiClient.getObjectMapper() + .readValue(response, new TypeReference>() {}); + List content = page.getContent(); + return content != null ? content : java.util.Collections.emptyList(); + } + + public CouponResponse createCoupon(CouponRequest request) throws Exception { + return apiClient.post("/api/v1/coupons", request, CouponResponse.class); + } + + public CouponResponse updateCoupon(Long id, CouponRequest request) throws Exception { + return apiClient.put("/api/v1/coupons/" + id, request, CouponResponse.class); + } + + public void deleteCoupon(Long id) throws Exception { + apiClient.delete("/api/v1/coupons/" + id); + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java new file mode 100644 index 00000000..af418826 --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java @@ -0,0 +1,246 @@ +package org.example.petshopdesktop.controllers; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.stage.Modality; +import javafx.stage.Stage; +import org.example.petshopdesktop.api.dto.coupon.CouponResponse; +import org.example.petshopdesktop.api.endpoints.CouponApi; +import org.example.petshopdesktop.controllers.dialogcontrollers.CouponDialogController; +import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.TableViewSupport; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CouponController { + + @FXML private Button btnAdd; + @FXML private Button btnEdit; + @FXML private Button btnDelete; + @FXML private Button btnRefresh; + @FXML private Label lblStatus; + @FXML private TextField txtSearch; + @FXML private ComboBox cbStatusFilter; + @FXML private ComboBox cbTypeFilter; + @FXML private TableView tvCoupons; + @FXML private TableColumn colId; + @FXML private TableColumn colCode; + @FXML private TableColumn colDiscount; + @FXML private TableColumn colMinOrder; + @FXML private TableColumn colStatus; + @FXML private TableColumn colStartsAt; + @FXML private TableColumn colEndsAt; + @FXML private TableColumn colUsageLimit; + + private final ObservableList allCoupons = FXCollections.observableArrayList(); + + @FXML + void initialize() { + btnEdit.setDisable(true); + btnDelete.setDisable(true); + + tvCoupons.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + colId.setCellValueFactory(new PropertyValueFactory<>("couponId")); + colCode.setCellValueFactory(new PropertyValueFactory<>("couponCode")); + + colDiscount.setCellValueFactory(data -> { + CouponResponse c = data.getValue(); + String formatted = formatDiscount(c); + return new javafx.beans.property.SimpleStringProperty(formatted); + }); + + colMinOrder.setCellValueFactory(data -> { + BigDecimal min = data.getValue().getMinOrderAmount(); + return new javafx.beans.property.SimpleStringProperty( + min != null ? "$" + min.toPlainString() : "—"); + }); + + colStatus.setCellValueFactory(data -> { + Boolean active = data.getValue().getActive(); + return new javafx.beans.property.SimpleStringProperty( + Boolean.TRUE.equals(active) ? "Active" : "Inactive"); + }); + + colStartsAt.setCellValueFactory(data -> + new javafx.beans.property.SimpleStringProperty(formatDate(data.getValue().getStartsAt()))); + + colEndsAt.setCellValueFactory(data -> + new javafx.beans.property.SimpleStringProperty(formatDate(data.getValue().getEndsAt()))); + + colUsageLimit.setCellValueFactory(data -> { + Integer limit = data.getValue().getUsageLimit(); + return new javafx.beans.property.SimpleStringProperty(limit != null ? limit.toString() : "Unlimited"); + }); + + cbStatusFilter.setItems(FXCollections.observableArrayList("All", "Active", "Inactive")); + cbStatusFilter.setValue("All"); + cbTypeFilter.setItems(FXCollections.observableArrayList("All", "FIXED", "PERCENT")); + cbTypeFilter.setValue("All"); + + tvCoupons.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> { + boolean hasSelection = newVal != null; + btnEdit.setDisable(!hasSelection); + btnDelete.setDisable(!hasSelection); + }); + + txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applyFilters()); + cbStatusFilter.valueProperty().addListener((obs, oldVal, newVal) -> applyFilters()); + cbTypeFilter.valueProperty().addListener((obs, oldVal, newVal) -> applyFilters()); + + TableViewSupport.installDoubleClickAction(tvCoupons, selected -> openDialog(selected, "Edit")); + + tvCoupons.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE + && tvCoupons.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + }); + + loadCoupons(); + } + + @FXML + void btnAddClicked(ActionEvent event) { + openDialog(null, "Add"); + } + + @FXML + void btnEditClicked(ActionEvent event) { + CouponResponse selected = tvCoupons.getSelectionModel().getSelectedItem(); + if (selected != null) { + openDialog(selected, "Edit"); + } + } + + @FXML + void btnDeleteClicked(ActionEvent event) { + List selected = tvCoupons.getSelectionModel().getSelectedItems(); + if (selected.isEmpty()) return; + + Alert question = new Alert(Alert.AlertType.CONFIRMATION); + question.setHeaderText("Confirm Delete"); + question.setContentText(selected.size() == 1 + ? "Are you sure you want to delete this coupon?" + : "Are you sure you want to delete " + selected.size() + " coupons?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); + Optional result = question.showAndWait(); + + if (result.isPresent() && result.get() == ButtonType.OK) { + List ids = selected.stream().map(CouponResponse::getCouponId).collect(Collectors.toList()); + try { + for (Long id : ids) { + CouponApi.getInstance().deleteCoupon(id); + } + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setHeaderText("Deleted"); + alert.setContentText("Successfully deleted " + ids.size() + " coupon(s)"); + alert.showAndWait(); + } catch (Exception e) { + ActivityLogger.getInstance().logException("CouponController.btnDeleteClicked", e, "Deleting coupons"); + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Delete Failed"); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + loadCoupons(); + btnEdit.setDisable(true); + btnDelete.setDisable(true); + } + } + + @FXML + void btnRefreshClicked(ActionEvent event) { + txtSearch.clear(); + cbStatusFilter.setValue("All"); + cbTypeFilter.setValue("All"); + tvCoupons.getSortOrder().clear(); + loadCoupons(); + TableViewSupport.flashStatus(lblStatus, "Refreshed"); + } + + private void loadCoupons() { + new Thread(() -> { + try { + List coupons = CouponApi.getInstance().listCoupons(); + Platform.runLater(() -> { + allCoupons.setAll(coupons != null ? coupons : java.util.Collections.emptyList()); + applyFilters(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("CouponController.loadCoupons", e, "Loading coupons"); + Platform.runLater(() -> { + lblStatus.setText("Failed to load coupons: " + e.getMessage()); + lblStatus.setVisible(true); + lblStatus.setManaged(true); + }); + } + }).start(); + } + + private void applyFilters() { + String search = txtSearch.getText() == null ? "" : txtSearch.getText().trim().toLowerCase(); + String status = cbStatusFilter.getValue(); + String type = cbTypeFilter.getValue(); + + List filtered = allCoupons.stream() + .filter(c -> search.isEmpty() || (c.getCouponCode() != null + && c.getCouponCode().toLowerCase().contains(search))) + .filter(c -> "All".equals(status) + || ("Active".equals(status) && Boolean.TRUE.equals(c.getActive())) + || ("Inactive".equals(status) && !Boolean.TRUE.equals(c.getActive()))) + .filter(c -> "All".equals(type) || type.equals(c.getDiscountType())) + .collect(Collectors.toList()); + + tvCoupons.setItems(FXCollections.observableArrayList(filtered)); + } + + private void openDialog(CouponResponse coupon, String mode) { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/example/petshopdesktop/dialogviews/coupon-dialog-view.fxml")); + Scene scene; + try { + scene = new Scene(loader.load()); + } catch (IOException e) { + ActivityLogger.getInstance().logException("CouponController.openDialog", e, "Loading coupon dialog"); + throw new RuntimeException(e); + } + CouponDialogController controller = loader.getController(); + controller.setMode(mode); + if ("Edit".equals(mode)) { + controller.displayCouponDetails(coupon); + } + Stage stage = new Stage(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setTitle("Add".equals(mode) ? "Add Coupon" : "Edit Coupon"); + stage.setScene(scene); + stage.showAndWait(); + loadCoupons(); + btnEdit.setDisable(true); + btnDelete.setDisable(true); + } + + private String formatDiscount(CouponResponse c) { + if (c.getDiscountValue() == null) return "—"; + if ("PERCENT".equals(c.getDiscountType())) { + return c.getDiscountValue().stripTrailingZeros().toPlainString() + "% OFF"; + } + return "$" + c.getDiscountValue().toPlainString() + " OFF"; + } + + private String formatDate(String isoDate) { + if (isoDate == null || isoDate.isBlank()) return "—"; + return isoDate.length() >= 10 ? isoDate.substring(0, 10) : isoDate; + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index c4af659f..9f14ec3f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -91,6 +91,9 @@ public class MainLayoutController { @FXML private Button btnAnalytics; + @FXML + private Button btnCoupons; + @FXML private Button btnLogout; @@ -187,6 +190,12 @@ public class MainLayoutController { updateButtons(btnActivityLogs); } + @FXML + void btnCouponsClicked(ActionEvent event) { + loadView("coupon-view.fxml"); + updateButtons(btnCoupons); + } + @FXML void btnServicesClicked(ActionEvent event) { loadView("service-view.fxml"); @@ -415,6 +424,11 @@ public class MainLayoutController { btnActivityLogs.setManaged(isAdmin); } + if (btnCoupons != null) { + btnCoupons.setVisible(isAdmin); + btnCoupons.setManaged(isAdmin); + } + btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales"); // Initial chat state and subscription @@ -466,6 +480,7 @@ public class MainLayoutController { btnStaffAccounts, btnAnalytics, btnActivityLogs, + btnCoupons, btnChat }; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java new file mode 100644 index 00000000..21fbc521 --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java @@ -0,0 +1,203 @@ +package org.example.petshopdesktop.controllers.dialogcontrollers; + +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; +import org.example.petshopdesktop.api.dto.coupon.CouponRequest; +import org.example.petshopdesktop.api.dto.coupon.CouponResponse; +import org.example.petshopdesktop.api.endpoints.CouponApi; +import org.example.petshopdesktop.util.ActivityLogger; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public class CouponDialogController { + + @FXML private Button btnSave; + @FXML private Button btnCancel; + @FXML private Label lblMode; + @FXML private Label lblCouponId; + @FXML private TextField txtCode; + @FXML private ComboBox cbDiscountType; + @FXML private TextField txtDiscountValue; + @FXML private TextField txtMinOrder; + @FXML private DatePicker dpStartsAt; + @FXML private DatePicker dpEndsAt; + @FXML private TextField txtUsageLimit; + @FXML private CheckBox chkActive; + + private String mode; + private Long couponId; + + @FXML + void initialize() { + cbDiscountType.setItems(FXCollections.observableArrayList("FIXED", "PERCENT")); + + dpEndsAt.setDisable(true); + + dpStartsAt.setDayCellFactory(picker -> new javafx.scene.control.DateCell() { + @Override + public void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + setDisable(empty || item.isBefore(LocalDate.now())); + } + }); + + dpStartsAt.valueProperty().addListener((obs, oldVal, newVal) -> { + if (newVal == null) { + dpEndsAt.setValue(null); + dpEndsAt.setDisable(true); + } else { + dpEndsAt.setDisable(false); + dpEndsAt.setDayCellFactory(picker -> new javafx.scene.control.DateCell() { + @Override + public void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + setDisable(empty || !item.isAfter(newVal)); + } + }); + if (dpEndsAt.getValue() != null && !dpEndsAt.getValue().isAfter(newVal)) { + dpEndsAt.setValue(null); + } + } + }); + + btnSave.setOnMouseClicked(this::handleSave); + btnCancel.setOnMouseClicked(this::handleCancel); + } + + public void setMode(String mode) { + this.mode = mode; + lblMode.setText(mode + " Coupon"); + lblCouponId.setVisible("Edit".equals(mode)); + lblCouponId.setManaged("Edit".equals(mode)); + } + + public void displayCouponDetails(CouponResponse coupon) { + if (coupon == null) return; + couponId = coupon.getCouponId(); + lblCouponId.setText("ID: " + couponId); + txtCode.setText(coupon.getCouponCode() != null ? coupon.getCouponCode() : ""); + cbDiscountType.setValue(coupon.getDiscountType()); + txtDiscountValue.setText(coupon.getDiscountValue() != null ? coupon.getDiscountValue().toPlainString() : ""); + txtMinOrder.setText(coupon.getMinOrderAmount() != null ? coupon.getMinOrderAmount().toPlainString() : ""); + txtUsageLimit.setText(coupon.getUsageLimit() != null ? coupon.getUsageLimit().toString() : ""); + chkActive.setSelected(Boolean.TRUE.equals(coupon.getActive())); + if (coupon.getStartsAt() != null && coupon.getStartsAt().length() >= 10) { + dpStartsAt.setValue(LocalDate.parse(coupon.getStartsAt().substring(0, 10))); + } + if (coupon.getEndsAt() != null && coupon.getEndsAt().length() >= 10 && dpStartsAt.getValue() != null) { + dpEndsAt.setValue(LocalDate.parse(coupon.getEndsAt().substring(0, 10))); + } + } + + private void handleSave(MouseEvent event) { + String errorMsg = ""; + + if (txtCode.getText() == null || txtCode.getText().trim().isEmpty()) { + errorMsg += "Coupon Code is required\n"; + } + if (cbDiscountType.getValue() == null) { + errorMsg += "Discount Type is required\n"; + } + if (txtDiscountValue.getText() == null || txtDiscountValue.getText().trim().isEmpty()) { + errorMsg += "Discount Value is required\n"; + } else { + try { + BigDecimal val = new BigDecimal(txtDiscountValue.getText().trim()); + if (val.compareTo(BigDecimal.ZERO) <= 0) { + errorMsg += "Discount Value must be greater than 0\n"; + } + } catch (NumberFormatException e) { + errorMsg += "Discount Value must be a valid number\n"; + } + } + if (!txtMinOrder.getText().trim().isEmpty()) { + try { + BigDecimal min = new BigDecimal(txtMinOrder.getText().trim()); + if (min.compareTo(BigDecimal.ZERO) < 0) { + errorMsg += "Min Order Amount must be 0 or greater\n"; + } + } catch (NumberFormatException e) { + errorMsg += "Min Order Amount must be a valid number\n"; + } + } + if (!txtUsageLimit.getText().trim().isEmpty()) { + try { + int limit = Integer.parseInt(txtUsageLimit.getText().trim()); + if (limit <= 0) { + errorMsg += "Usage Limit must be a positive number\n"; + } + } catch (NumberFormatException e) { + errorMsg += "Usage Limit must be a valid integer\n"; + } + } + if (dpStartsAt.getValue() != null && dpEndsAt.getValue() != null + && !dpEndsAt.getValue().isAfter(dpStartsAt.getValue())) { + errorMsg += "Ends At must be after Starts At\n"; + } + + if (!errorMsg.isEmpty()) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Input Error"); + alert.setContentText(errorMsg); + alert.showAndWait(); + return; + } + + CouponRequest request = buildRequest(); + try { + if ("Add".equals(mode)) { + CouponApi.getInstance().createCoupon(request); + } else { + CouponApi.getInstance().updateCoupon(couponId, request); + } + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setHeaderText("Saved"); + alert.setContentText(mode + " succeeded"); + alert.showAndWait(); + closeStage(event); + } catch (Exception e) { + ActivityLogger.getInstance().logException("CouponDialogController.handleSave", e, mode + " coupon"); + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Operation Error"); + alert.setContentText(mode + " failed: " + e.getMessage()); + alert.showAndWait(); + } + } + + private void handleCancel(MouseEvent event) { + closeStage(event); + } + + private CouponRequest buildRequest() { + CouponRequest request = new CouponRequest(); + request.setCouponCode(txtCode.getText().trim().toUpperCase()); + request.setDiscountType(cbDiscountType.getValue()); + request.setDiscountValue(new BigDecimal(txtDiscountValue.getText().trim())); + request.setActive(chkActive.isSelected()); + + if (!txtMinOrder.getText().trim().isEmpty()) { + request.setMinOrderAmount(new BigDecimal(txtMinOrder.getText().trim())); + } + if (!txtUsageLimit.getText().trim().isEmpty()) { + request.setUsageLimit(Integer.parseInt(txtUsageLimit.getText().trim())); + } + if (dpStartsAt.getValue() != null) { + request.setStartsAt(dpStartsAt.getValue().toString() + "T00:00:00"); + } + if (dpEndsAt.getValue() != null) { + request.setEndsAt(dpEndsAt.getValue().toString() + "T23:59:59"); + } + return request; + } + + private void closeStage(MouseEvent event) { + Node node = (Node) event.getSource(); + Stage stage = (Stage) node.getScene().getWindow(); + stage.close(); + } +} diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/coupon-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/coupon-dialog-view.fxml new file mode 100644 index 00000000..36ab4233 --- /dev/null +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/coupon-dialog-view.fxml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml index 77800377..a9478744 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml @@ -236,6 +236,14 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +